113 lines
3.4 KiB
Svelte
113 lines
3.4 KiB
Svelte
<script lang="ts">
|
|
import { goto } from '$app/navigation';
|
|
import type { PageProps } from './$types';
|
|
|
|
let { data }: PageProps = $props();
|
|
|
|
function displayCategoryName(category: { name: string; displayName?: string }) {
|
|
return category.displayName ?? category.name;
|
|
}
|
|
|
|
let search = $state('');
|
|
let categoryId: number | null = $state(null);
|
|
let initialized = $state(false);
|
|
|
|
$effect(() => {
|
|
if (!initialized) {
|
|
search = data.search;
|
|
categoryId = data.categoryId;
|
|
initialized = true;
|
|
}
|
|
});
|
|
|
|
function applyFilters() {
|
|
const params = new URLSearchParams();
|
|
if (search) params.set('search', search);
|
|
if (categoryId) params.set('category', String(categoryId));
|
|
goto(`/proclamations?${params.toString()}`, { replaceState: true });
|
|
}
|
|
|
|
function clearFilters() {
|
|
search = '';
|
|
categoryId = null;
|
|
goto('/proclamations', { replaceState: true });
|
|
}
|
|
</script>
|
|
|
|
<div class="flex h-full min-h-0 flex-col gap-6 p-6">
|
|
<h1 class="text-2xl font-semibold text-gray-900">Proclamations</h1>
|
|
|
|
<!-- Filters -->
|
|
<div class="flex flex-wrap items-end gap-4">
|
|
<div>
|
|
<label for="search" class="block text-sm font-medium text-gray-700">Search</label>
|
|
<input
|
|
type="text"
|
|
id="search"
|
|
bind:value={search}
|
|
placeholder="Search by name..."
|
|
class="mt-1 rounded-lg border border-gray-300 px-3 py-2 shadow-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500"
|
|
onkeydown={(e) => e.key === 'Enter' && applyFilters()}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label for="category" class="block text-sm font-medium text-gray-700">Category</label>
|
|
<select
|
|
id="category"
|
|
bind:value={categoryId}
|
|
onchange={applyFilters}
|
|
class="mt-1 rounded-lg border border-gray-300 px-3 py-2 shadow-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500"
|
|
>
|
|
<option value={null}>All Categories</option>
|
|
{#each data.categories as cat}
|
|
<option value={cat.id}>{displayCategoryName(cat)}</option>
|
|
{/each}
|
|
</select>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
class="rounded-lg bg-blue-600 px-4 py-2 font-medium text-white transition-colors hover:bg-blue-700"
|
|
onclick={applyFilters}
|
|
>
|
|
Search
|
|
</button>
|
|
{#if data.search || data.categoryId}
|
|
<button
|
|
type="button"
|
|
class="rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50"
|
|
onclick={clearFilters}
|
|
>
|
|
Clear
|
|
</button>
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- Results -->
|
|
{#if data.proclamations.length > 0}
|
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
{#each data.proclamations as proclamation}
|
|
<a
|
|
href="/proclamations/{proclamation.id}"
|
|
class="flex flex-col rounded-lg border border-gray-200 bg-white p-5 shadow-sm transition-shadow hover:shadow-md"
|
|
>
|
|
<h2 class="text-lg font-medium text-gray-900">{proclamation.name}</h2>
|
|
<p class="mt-1 text-sm text-gray-500">{displayCategoryName(proclamation.category)}</p>
|
|
<div class="mt-auto flex items-center justify-between pt-4">
|
|
<span class="text-sm text-purple-600">
|
|
{proclamation.solfeggioFrequency.value} Hz —
|
|
{proclamation.solfeggioFrequency.name}
|
|
</span>
|
|
<span class="font-semibold text-gray-900">
|
|
${proclamation.price.toFixed(2)}
|
|
</span>
|
|
</div>
|
|
</a>
|
|
{/each}
|
|
</div>
|
|
{:else}
|
|
<div class="flex flex-1 items-center justify-center">
|
|
<p class="text-lg text-gray-500">No proclamations found.</p>
|
|
</div>
|
|
{/if}
|
|
</div>
|