feat(05-03): integrate filtering into EntryList with flat list mode

- Add filters and searchQuery props to EntryList
- Apply filterEntries via $derived for reactive filtering
- Display flat list (no pinned separation) when filtering
- Show search-specific empty state when no matches
- Pass searchQuery to EntryCard for highlighting
This commit is contained in:
Thomas Richter
2026-01-31 17:17:31 +01:00
parent af61b10a59
commit 9642b51ef8

View File

@@ -1,5 +1,8 @@
<script lang="ts"> <script lang="ts">
import type { Entry, Image, Tag } from '$lib/server/db/schema'; import type { Entry, Image, Tag } from '$lib/server/db/schema';
import type { SearchFilters } from '$lib/types/search';
import { filterEntries } from '$lib/utils/filterEntries';
import { hasActiveFilters } from '$lib/types/search';
import EntryCard from './EntryCard.svelte'; import EntryCard from './EntryCard.svelte';
interface EntryWithData extends Entry { interface EntryWithData extends Entry {
@@ -10,27 +13,50 @@
interface Props { interface Props {
entries: EntryWithData[]; entries: EntryWithData[];
availableTags: Tag[]; availableTags: Tag[];
filters: SearchFilters;
searchQuery: string;
} }
let { entries, availableTags }: Props = $props(); let { entries, availableTags, filters, searchQuery }: Props = $props();
// Separate entries into pinned and unpinned // Apply filtering
let pinnedEntries = $derived(entries.filter(e => e.pinned)); let filteredEntries = $derived(filterEntries(entries, filters));
let unpinnedEntries = $derived(entries.filter(e => !e.pinned)); let isFiltering = $derived(hasActiveFilters(filters));
// Separate entries into pinned and unpinned (only used when not filtering)
let pinnedEntries = $derived(filteredEntries.filter((e) => e.pinned));
let unpinnedEntries = $derived(filteredEntries.filter((e) => !e.pinned));
</script> </script>
{#if entries.length === 0} {#if filteredEntries.length === 0}
<div class="text-center py-12 text-gray-500"> {#if isFiltering}
<p class="text-lg">No entries yet</p> <div class="text-center py-12 text-gray-500">
<p class="text-sm mt-1">Use the capture bar below to add your first entry</p> <p class="text-lg">No entries match your search</p>
<p class="text-sm mt-1">Try adjusting your filters or search term</p>
</div>
{:else}
<div class="text-center py-12 text-gray-500">
<p class="text-lg">No entries yet</p>
<p class="text-sm mt-1">Use the capture bar below to add your first entry</p>
</div>
{/if}
{:else if isFiltering}
<!-- Flat list when filtering (no pinned/unpinned separation) -->
<div class="divide-y divide-gray-100 md:divide-y-0 md:space-y-3">
{#each filteredEntries as entry (entry.id)}
<EntryCard {entry} {availableTags} {searchQuery} />
{/each}
</div> </div>
{:else} {:else}
<!-- Normal view with pinned/unpinned separation -->
{#if pinnedEntries.length > 0} {#if pinnedEntries.length > 0}
<div class="mb-4"> <div class="mb-4">
<h2 class="text-sm font-medium text-gray-500 uppercase tracking-wide mb-2 px-4 md:px-0">Pinned</h2> <h2 class="text-sm font-medium text-gray-500 uppercase tracking-wide mb-2 px-4 md:px-0">
Pinned
</h2>
<div class="divide-y divide-gray-100 md:divide-y-0 md:space-y-3"> <div class="divide-y divide-gray-100 md:divide-y-0 md:space-y-3">
{#each pinnedEntries as entry (entry.id)} {#each pinnedEntries as entry (entry.id)}
<EntryCard {entry} {availableTags} /> <EntryCard {entry} {availableTags} {searchQuery} />
{/each} {/each}
</div> </div>
</div> </div>
@@ -39,7 +65,7 @@
{#if unpinnedEntries.length > 0} {#if unpinnedEntries.length > 0}
<div class="divide-y divide-gray-100 md:divide-y-0 md:space-y-3"> <div class="divide-y divide-gray-100 md:divide-y-0 md:space-y-3">
{#each unpinnedEntries as entry (entry.id)} {#each unpinnedEntries as entry (entry.id)}
<EntryCard {entry} {availableTags} /> <EntryCard {entry} {availableTags} {searchQuery} />
{/each} {/each}
</div> </div>
{/if} {/if}