1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-07-23 23:09:37 +02:00
AdventureLog/frontend/src/lib/components/CollectionCard.svelte

239 lines
6.9 KiB
Svelte
Raw Normal View History

2024-07-10 13:36:51 -04:00
<script lang="ts">
import { createEventDispatcher } from 'svelte';
2024-07-10 13:49:40 -04:00
import Launch from '~icons/mdi/launch';
2024-07-15 18:01:49 -04:00
import FileDocumentEdit from '~icons/mdi/file-document-edit';
2024-08-07 13:01:12 -04:00
import ArchiveArrowDown from '~icons/mdi/archive-arrow-down';
import ArchiveArrowUp from '~icons/mdi/archive-arrow-up';
import ShareVariant from '~icons/mdi/share-variant';
2024-07-15 18:01:49 -04:00
2024-07-10 13:36:51 -04:00
import { goto } from '$app/navigation';
import type { Adventure, Collection } from '$lib/types';
2024-07-15 12:09:20 -04:00
import { addToast } from '$lib/toasts';
2024-10-28 15:10:14 -04:00
import { t } from 'svelte-i18n';
2024-07-16 09:12:53 -04:00
import Plus from '~icons/mdi/plus';
import DotsHorizontal from '~icons/mdi/dots-horizontal';
import TrashCan from '~icons/mdi/trashcan';
2024-08-07 13:09:20 -04:00
import DeleteWarning from './DeleteWarning.svelte';
2024-09-08 14:29:27 -04:00
import ShareModal from './ShareModal.svelte';
import CardCarousel from './CardCarousel.svelte';
2024-07-16 09:12:53 -04:00
2024-07-10 13:36:51 -04:00
const dispatch = createEventDispatcher();
2024-07-16 09:26:45 -04:00
export let type: String | undefined | null;
2024-09-08 14:29:27 -04:00
let isShareModalOpen: boolean = false;
2024-07-16 09:12:53 -04:00
2024-07-15 18:01:49 -04:00
function editAdventure() {
dispatch('edit', collection);
}
2024-08-07 13:01:12 -04:00
async function archiveCollection(is_archived: boolean) {
console.log(JSON.stringify({ is_archived: is_archived }));
let res = await fetch(`/api/collections/${collection.id}/`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ is_archived: is_archived })
});
if (res.ok) {
2024-10-28 15:10:14 -04:00
if (is_archived) {
addToast('info', $t('adventures.archived_collection_message'));
} else {
addToast('info', $t('adventures.unarchived_collection_message'));
}
2024-08-07 13:01:12 -04:00
dispatch('delete', collection.id);
} else {
console.log('Error archiving collection');
2024-08-07 13:01:12 -04:00
}
}
2024-07-15 09:36:07 -04:00
export let collection: Collection;
2024-07-10 13:36:51 -04:00
2024-07-15 12:09:20 -04:00
async function deleteCollection() {
let res = await fetch(`/api/collections/${collection.id}`, {
method: 'DELETE'
2024-07-15 12:09:20 -04:00
});
if (res.ok) {
2024-10-28 15:10:14 -04:00
addToast('info', $t('adventures.delete_collection_success'));
2024-07-15 12:09:20 -04:00
dispatch('delete', collection.id);
} else {
2024-08-07 16:34:03 -04:00
console.log('Error deleting collection');
2024-07-15 12:09:20 -04:00
}
}
2024-08-07 13:09:20 -04:00
let isWarningModalOpen: boolean = false;
2024-07-10 13:36:51 -04:00
</script>
2024-08-07 13:09:20 -04:00
{#if isWarningModalOpen}
<DeleteWarning
2024-10-28 15:10:14 -04:00
title={$t('adventures.delete_collection')}
button_text={$t('adventures.delete')}
description={$t('adventures.delete_collection_warning')}
2024-08-07 13:09:20 -04:00
is_warning={true}
on:close={() => (isWarningModalOpen = false)}
on:confirm={deleteCollection}
/>
{/if}
2024-09-08 14:29:27 -04:00
{#if isShareModalOpen}
<ShareModal {collection} on:close={() => (isShareModalOpen = false)} />
{/if}
2024-07-10 13:36:51 -04:00
<div
class="card w-full max-w-md bg-base-300 shadow-2xl hover:shadow-3xl transition-all duration-300 border border-base-300 hover:border-primary/20 group"
2024-07-10 13:36:51 -04:00
>
<!-- Image Carousel -->
<div class="relative overflow-hidden rounded-t-2xl">
<CardCarousel adventures={collection.adventures} />
<!-- Badge Overlay -->
<div class="absolute top-4 left-4 flex flex-col gap-2">
<div class="badge badge-sm badge-secondary shadow-lg">
{collection.is_public ? $t('adventures.public') : $t('adventures.private')}
</div>
{#if collection.is_archived}
<div class="badge badge-sm badge-warning shadow-lg">
{$t('adventures.archived')}
</div>
{/if}
</div>
</div>
<!-- Content -->
<div class="card-body p-6 space-y-4">
<!-- Title -->
<div class="space-y-3">
2024-08-08 22:12:06 -04:00
<button
on:click={() => goto(`/collections/${collection.id}`)}
class="text-xl font-bold text-left hover:text-primary transition-colors duration-200 line-clamp-2 group-hover:underline"
2024-08-08 22:12:06 -04:00
>
{collection.name}
</button>
<!-- Adventure Count -->
<p class="text-sm text-base-content/70">
{collection.adventures.length}
{$t('navbar.adventures')}
</p>
<!-- Date Range -->
{#if collection.start_date && collection.end_date}
<p class="text-sm font-medium">
{$t('adventures.dates')}:
{new Date(collection.start_date).toLocaleDateString(undefined, { timeZone: 'UTC' })}
{new Date(collection.end_date).toLocaleDateString(undefined, { timeZone: 'UTC' })}
</p>
<p class="text-sm text-base-content/60">
{$t('adventures.duration')}: {Math.floor(
(new Date(collection.end_date).getTime() - new Date(collection.start_date).getTime()) /
(1000 * 60 * 60 * 24)
) + 1} days
</p>
2024-08-08 22:12:06 -04:00
{/if}
</div>
<!-- Actions -->
<div class="pt-4 border-t border-base-300">
{#if type == 'link'}
<button class="btn btn-primary btn-block" on:click={() => dispatch('link', collection.id)}>
<Plus class="w-4 h-4" />
{$t('adventures.add_to_collection')}
</button>
{:else}
<div class="flex justify-between items-center">
<button
class="btn btn-neutral btn-sm flex-1 mr-2"
on:click={() => goto(`/collections/${collection.id}`)}
>
<Launch class="w-4 h-4" />
{$t('adventures.open_details')}
</button>
<div class="dropdown dropdown-end">
<button type="button" class="btn btn-square btn-sm btn-base-300">
<DotsHorizontal class="w-5 h-5" />
</button>
<ul
class="dropdown-content menu bg-base-100 rounded-box z-[1] w-64 p-2 shadow-xl border border-base-300"
>
{#if type != 'viewonly'}
<li>
<button class="flex items-center gap-2" on:click={editAdventure}>
<FileDocumentEdit class="w-4 h-4" />
{$t('adventures.edit_collection')}
</button>
</li>
<li>
<button
class="flex items-center gap-2"
on:click={() => (isShareModalOpen = true)}
>
<ShareVariant class="w-4 h-4" />
{$t('adventures.share')}
</button>
</li>
{#if collection.is_archived}
<li>
<button
class="flex items-center gap-2"
on:click={() => archiveCollection(false)}
>
<ArchiveArrowUp class="w-4 h-4" />
{$t('adventures.unarchive')}
</button>
</li>
{:else}
<li>
<button
class="flex items-center gap-2"
on:click={() => archiveCollection(true)}
>
<ArchiveArrowDown class="w-4 h-4" />
{$t('adventures.archive')}
</button>
</li>
{/if}
<div class="divider my-1"></div>
<li>
<button
id="delete_collection"
data-umami-event="Delete Collection"
class="text-error flex items-center gap-2"
on:click={() => (isWarningModalOpen = true)}
>
<TrashCan class="w-4 h-4" />
{$t('adventures.delete')}
</button>
</li>
{/if}
{#if type == 'viewonly'}
<li>
<button
class="flex items-center gap-2"
on:click={() => goto(`/collections/${collection.id}`)}
>
<Launch class="w-4 h-4" />
{$t('adventures.open_details')}
</button>
</li>
{/if}
</ul>
</div>
</div>
{/if}
2024-07-10 13:36:51 -04:00
</div>
</div>
</div>
<style>
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
</style>