mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 21:41:09 +02:00
feat(zitare): add search and sort to category detail page
Add inline search field and sort-by-author option to category pages. Also extract hardcoded German strings and respect display settings. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0c479b3e88
commit
40b55eb65f
3 changed files with 88 additions and 11 deletions
|
|
@ -38,7 +38,12 @@
|
|||
"courage": "Mut",
|
||||
"hope": "Hoffnung",
|
||||
"nature": "Natur",
|
||||
"quotes": "{count} Zitate"
|
||||
"quotes": "{count} Zitate",
|
||||
"notFound": "Kategorie nicht gefunden",
|
||||
"backToCategories": "Zurück zu Kategorien",
|
||||
"searchInCategory": "In dieser Kategorie suchen...",
|
||||
"sortByAuthor": "Nach Autor",
|
||||
"sortByDefault": "Standard"
|
||||
},
|
||||
"favorites": {
|
||||
"title": "Favoriten",
|
||||
|
|
|
|||
|
|
@ -38,7 +38,12 @@
|
|||
"courage": "Courage",
|
||||
"hope": "Hope",
|
||||
"nature": "Nature",
|
||||
"quotes": "{count} quotes"
|
||||
"quotes": "{count} quotes",
|
||||
"notFound": "Category not found",
|
||||
"backToCategories": "Back to categories",
|
||||
"searchInCategory": "Search in this category...",
|
||||
"sortByAuthor": "By author",
|
||||
"sortByDefault": "Default"
|
||||
},
|
||||
"favorites": {
|
||||
"title": "Favorites",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@
|
|||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { getQuotesByCategory, CATEGORIES, type Category } from '@zitare/content';
|
||||
import { getQuotesByCategory, CATEGORIES, type Category, type Quote } from '@zitare/content';
|
||||
import { quotesStore } from '$lib/stores/quotes.svelte';
|
||||
import { zitareSettings } from '$lib/stores/settings.svelte';
|
||||
import QuoteCard from '$lib/components/QuoteCard.svelte';
|
||||
|
||||
// Get category from URL
|
||||
|
|
@ -14,6 +16,31 @@
|
|||
// Get quotes for this category
|
||||
let quotes = $derived(isValidCategory ? getQuotesByCategory(category) : []);
|
||||
|
||||
// Search & sort state
|
||||
let searchTerm = $state('');
|
||||
let sortBy = $state<'default' | 'author'>('default');
|
||||
|
||||
// Filtered and sorted quotes
|
||||
let displayedQuotes = $derived<Quote[]>(() => {
|
||||
let filtered = quotes;
|
||||
|
||||
// Filter by search
|
||||
if (searchTerm.length >= 2) {
|
||||
const lower = searchTerm.toLowerCase();
|
||||
filtered = filtered.filter(
|
||||
(q) =>
|
||||
quotesStore.getText(q).toLowerCase().includes(lower) ||
|
||||
q.author.toLowerCase().includes(lower)
|
||||
);
|
||||
}
|
||||
|
||||
// Sort
|
||||
if (sortBy === 'author') {
|
||||
return [...filtered].sort((a, b) => a.author.localeCompare(b.author));
|
||||
}
|
||||
return filtered;
|
||||
});
|
||||
|
||||
// Category labels
|
||||
const categoryLabels: Record<Category, string> = {
|
||||
weisheit: 'categories.wisdom',
|
||||
|
|
@ -30,7 +57,9 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Zitare - {isValidCategory ? $_(categoryLabels[category]) : 'Kategorie'}</title>
|
||||
<title
|
||||
>Zitare - {isValidCategory ? $_(categoryLabels[category]) : $_('categories.notFound')}</title
|
||||
>
|
||||
</svelte:head>
|
||||
|
||||
<div class="max-w-3xl mx-auto">
|
||||
|
|
@ -47,20 +76,58 @@
|
|||
|
||||
{#if isValidCategory}
|
||||
<h1 class="text-3xl font-bold text-foreground mb-2">{$_(categoryLabels[category])}</h1>
|
||||
<p class="text-foreground-secondary mb-8">
|
||||
<p class="text-foreground-secondary mb-6">
|
||||
{$_('categories.quotes', { values: { count: quotes.length } })}
|
||||
</p>
|
||||
|
||||
<div class="space-y-6">
|
||||
{#each quotes as quote (quote.id)}
|
||||
<QuoteCard {quote} showSource />
|
||||
{/each}
|
||||
<!-- Search & Sort Bar -->
|
||||
<div class="flex gap-3 mb-8">
|
||||
<div class="relative flex-1">
|
||||
<svg
|
||||
class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-foreground-muted"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
||||
/>
|
||||
</svg>
|
||||
<input
|
||||
type="text"
|
||||
placeholder={$_('categories.searchInCategory')}
|
||||
bind:value={searchTerm}
|
||||
class="w-full pl-10 pr-4 py-2.5 rounded-xl bg-surface-elevated border border-border text-foreground text-sm focus:outline-none focus:border-primary transition-colors"
|
||||
/>
|
||||
</div>
|
||||
<select
|
||||
bind:value={sortBy}
|
||||
class="px-3 py-2.5 rounded-xl bg-surface-elevated border border-border text-foreground text-sm"
|
||||
>
|
||||
<option value="default">{$_('categories.sortByDefault')}</option>
|
||||
<option value="author">{$_('categories.sortByAuthor')}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{#if displayedQuotes.length === 0 && searchTerm.length >= 2}
|
||||
<div class="text-center py-12">
|
||||
<p class="text-foreground-secondary">{$_('search.noResults')}</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="space-y-6">
|
||||
{#each displayedQuotes as quote (quote.id)}
|
||||
<QuoteCard {quote} showSource={zitareSettings.showSource} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="text-center py-12">
|
||||
<p class="text-foreground-secondary">Kategorie nicht gefunden</p>
|
||||
<p class="text-foreground-secondary">{$_('categories.notFound')}</p>
|
||||
<button onclick={() => goto('/categories')} class="mt-4 text-primary hover:underline">
|
||||
Zurück zu Kategorien
|
||||
{$_('categories.backToCategories')}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue