feat(collections): enhance collections page with sorting, filtering, and pagination features

- Updated the collections loading logic to include sorting and pagination parameters from the URL.
- Refactored the collections page to manage owned, shared, and archived collections with a tabbed interface.
- Added sorting functionality to allow users to sort collections by different attributes.
- Implemented a sidebar for filtering and sorting options.
- Improved the UI for better user experience, including a floating action button for creating new collections.
- Added a not found page for collections that do not exist, enhancing error handling.
This commit is contained in:
Sean Morley
2025-06-13 12:11:42 -04:00
parent 7eb96bcc2a
commit 14eb4ca802
23 changed files with 1691 additions and 1251 deletions

View File

@@ -112,7 +112,6 @@
location: null,
images: [],
user_id: null,
collection: collection?.id || null,
category: {
id: '',
name: '',
@@ -138,7 +137,6 @@
location: adventureToEdit?.location || null,
images: adventureToEdit?.images || [],
user_id: adventureToEdit?.user_id || null,
collection: adventureToEdit?.collection || collection?.id || null,
visits: adventureToEdit?.visits || [],
is_visited: adventureToEdit?.is_visited || false,
category: adventureToEdit?.category || {
@@ -628,7 +626,7 @@
<p class="text-red-500">{wikiError}</p>
</div>
</div>
{#if !adventure?.collection}
{#if adventure.collections && adventure.collections.length == 0}
<div>
<div class="form-control flex items-start mt-1">
<label class="label cursor-pointer flex items-start space-x-2">

View File

@@ -25,7 +25,7 @@
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<ul
tabindex="0"
class="dropdown-content z-[1] text-neutral-200 menu p-2 shadow bg-neutral mt-2 rounded-box w-52"
class="dropdown-content z-[999] text-neutral-200 menu p-2 shadow bg-neutral mt-2 rounded-box w-52"
>
<!-- svelte-ignore a11y-missing-attribute -->
<!-- svelte-ignore a11y-missing-attribute -->

View File

@@ -9,7 +9,7 @@
import ShareVariant from '~icons/mdi/share-variant';
import { goto } from '$app/navigation';
import type { Adventure, Collection } from '$lib/types';
import type { Adventure, Collection, User } from '$lib/types';
import { addToast } from '$lib/toasts';
import { t } from 'svelte-i18n';
@@ -25,6 +25,7 @@
export let type: String | undefined | null;
export let linkedCollectionList: string[] | null = null;
export let user: User | null;
let isShareModalOpen: boolean = false;
function editAdventure() {
@@ -168,76 +169,78 @@
<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}
{#if user && user.uuid == collection.user_id}
<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={() => archiveCollection(false)}
>
<ArchiveArrowUp class="w-4 h-4" />
{$t('adventures.unarchive')}
<button class="flex items-center gap-2" on:click={editAdventure}>
<FileDocumentEdit class="w-4 h-4" />
{$t('adventures.edit_collection')}
</button>
</li>
{:else}
<li>
<button
class="flex items-center gap-2"
on:click={() => archiveCollection(true)}
on:click={() => (isShareModalOpen = true)}
>
<ArchiveArrowDown class="w-4 h-4" />
{$t('adventures.archive')}
<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}
<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>
{#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>
{/if}
</div>
{/if}
</div>

View File

@@ -219,6 +219,28 @@
{$t('about.close')}
</button>
</div>
{#if collection.is_public && collection.id}
<div class="bg-neutral p-4 mt-2 rounded-md shadow-sm text-neutral-content">
<p class=" font-semibold">{$t('adventures.share_collection')}</p>
<div class="flex items-center justify-between">
<p class="text-card-foreground font-mono">
{window.location.origin}/collections/{collection.id}
</p>
<button
type="button"
on:click={() => {
navigator.clipboard.writeText(
`${window.location.origin}/collections/${collection.id}`
);
}}
class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 h-10 px-4 py-2"
>
{$t('adventures.copy_link')}
</button>
</div>
</div>
{/if}
</form>
</div>
</div>

View File

@@ -277,7 +277,7 @@
{#if data.user}
<Avatar user={data.user} />
{/if}
<div class="dropdown dropdown-bottom dropdown-end">
<div class="dropdown dropdown-bottom dropdown-end z-[999]">
<div tabindex="0" role="button" class="btn m-1 p-2">
<DotsHorizontal class="w-6 h-6" />
</div>