refactor: abstract dashboard lists into components
This commit is contained in:
157
src/lib/components/dashboard/WishlistSection.svelte
Normal file
157
src/lib/components/dashboard/WishlistSection.svelte
Normal file
@@ -0,0 +1,157 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import WishlistGrid from '$lib/components/dashboard/WishlistGrid.svelte';
|
||||
import WishlistCard from '$lib/components/dashboard/WishlistCard.svelte';
|
||||
import { enhance } from '$app/forms';
|
||||
import { Star } from 'lucide-svelte';
|
||||
import { languageStore } from '$lib/stores/language.svelte';
|
||||
import SearchBar from '$lib/components/ui/SearchBar.svelte';
|
||||
import UnlockButton from '$lib/components/ui/UnlockButton.svelte';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
type WishlistItem = any; // You can make this more specific based on your types
|
||||
|
||||
let {
|
||||
title,
|
||||
description,
|
||||
items,
|
||||
emptyMessage,
|
||||
emptyDescription,
|
||||
emptyActionLabel,
|
||||
emptyActionHref,
|
||||
showCreateButton = false,
|
||||
hideIfEmpty = false,
|
||||
actions
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
items: WishlistItem[];
|
||||
emptyMessage: string;
|
||||
emptyDescription?: string;
|
||||
emptyActionLabel?: string;
|
||||
emptyActionHref?: string;
|
||||
showCreateButton?: boolean;
|
||||
hideIfEmpty?: boolean;
|
||||
actions: Snippet<[WishlistItem, boolean]>; // item, unlocked
|
||||
} = $props();
|
||||
|
||||
const t = $derived(languageStore.t);
|
||||
|
||||
let unlocked = $state(false);
|
||||
let searchQuery = $state('');
|
||||
|
||||
// Filter items based on search query
|
||||
const filteredItems = $derived(() => {
|
||||
if (!searchQuery.trim()) return items;
|
||||
|
||||
return items.filter(item => {
|
||||
const title = item.title || item.wishlist?.title || '';
|
||||
const description = item.description || item.wishlist?.description || '';
|
||||
const query = searchQuery.toLowerCase();
|
||||
|
||||
return title.toLowerCase().includes(query) || description.toLowerCase().includes(query);
|
||||
});
|
||||
});
|
||||
|
||||
// Sort items by favorite, end date, then created date
|
||||
const sortedItems = $derived(() => {
|
||||
return [...filteredItems()].sort((a, b) => {
|
||||
// Handle both direct wishlists and saved wishlists
|
||||
const aItem = a.wishlist || a;
|
||||
const bItem = b.wishlist || b;
|
||||
|
||||
// Sort by favorite first
|
||||
if (a.isFavorite && !b.isFavorite) return -1;
|
||||
if (!a.isFavorite && b.isFavorite) return 1;
|
||||
|
||||
// Then by end date
|
||||
const aHasEndDate = !!aItem.endDate;
|
||||
const bHasEndDate = !!bItem.endDate;
|
||||
|
||||
if (aHasEndDate && !bHasEndDate) return -1;
|
||||
if (!aHasEndDate && bHasEndDate) return 1;
|
||||
|
||||
if (aHasEndDate && bHasEndDate) {
|
||||
return new Date(aItem.endDate!).getTime() - new Date(bItem.endDate!).getTime();
|
||||
}
|
||||
|
||||
// Finally by created date (most recent first)
|
||||
const aCreatedAt = a.createdAt || aItem.createdAt;
|
||||
const bCreatedAt = b.createdAt || bItem.createdAt;
|
||||
return new Date(bCreatedAt).getTime() - new Date(aCreatedAt).getTime();
|
||||
});
|
||||
});
|
||||
|
||||
function formatEndDate(date: Date | string | null): string | null {
|
||||
if (!date) return null;
|
||||
const d = new Date(date);
|
||||
return d.toLocaleDateString(languageStore.t.date.format.short, { year: 'numeric', month: 'short', day: 'numeric' });
|
||||
}
|
||||
|
||||
function getWishlistDescription(item: any): string | null {
|
||||
const wishlist = item.wishlist || item;
|
||||
if (!wishlist) return null;
|
||||
|
||||
const lines: string[] = [];
|
||||
|
||||
const topItems = wishlist.items?.slice(0, 3).map((i: any) => i.title) || [];
|
||||
if (topItems.length > 0) {
|
||||
lines.push(topItems.join(', '));
|
||||
}
|
||||
|
||||
if (wishlist.user?.name || wishlist.user?.username) {
|
||||
const ownerName = wishlist.user.name || wishlist.user.username;
|
||||
lines.push(`${t.dashboard.by} ${ownerName}`);
|
||||
}
|
||||
|
||||
if (wishlist.endDate) {
|
||||
lines.push(`${t.dashboard.ends}: ${formatEndDate(wishlist.endDate)}`);
|
||||
}
|
||||
|
||||
return lines.length > 0 ? lines.join('\n') : null;
|
||||
}
|
||||
|
||||
// Hide entire section if hideIfEmpty is true and there are no items
|
||||
const shouldShow = $derived(() => {
|
||||
return !hideIfEmpty || items.length > 0;
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if shouldShow()}
|
||||
<WishlistGrid
|
||||
{title}
|
||||
{description}
|
||||
items={sortedItems() || []}
|
||||
{emptyMessage}
|
||||
{emptyDescription}
|
||||
{emptyActionLabel}
|
||||
{emptyActionHref}
|
||||
>
|
||||
{#snippet headerAction()}
|
||||
<div class="flex flex-col sm:flex-row gap-2">
|
||||
{#if showCreateButton}
|
||||
<Button onclick={() => (window.location.href = '/')}>{t.dashboard.createNew}</Button>
|
||||
{/if}
|
||||
<UnlockButton bind:unlocked />
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#snippet searchBar()}
|
||||
{#if items.length > 0}
|
||||
<SearchBar bind:value={searchQuery} />
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
{#snippet children(item)}
|
||||
{@const wishlist = item.wishlist || item}
|
||||
<WishlistCard
|
||||
title={wishlist.title}
|
||||
description={getWishlistDescription(item)}
|
||||
itemCount={wishlist.items?.length || 0}
|
||||
color={wishlist.color}
|
||||
>
|
||||
{@render actions(item, unlocked)}
|
||||
</WishlistCard>
|
||||
{/snippet}
|
||||
</WishlistGrid>
|
||||
{/if}
|
||||
Reference in New Issue
Block a user