innercontext/frontend/src/routes/routines/grooming-schedule/+page.svelte

230 lines
8.1 KiB
Svelte

<script lang="ts">
import { enhance } from '$app/forms';
import { resolve } from '$app/paths';
import type { ActionData, PageData } from './$types';
import { m } from '$lib/paraglide/messages.js';
import { Badge } from '$lib/components/ui/badge';
import { Button } from '$lib/components/ui/button';
import { Card, CardContent } from '$lib/components/ui/card';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import { Separator } from '$lib/components/ui/separator';
import { ArrowLeft } from 'lucide-svelte';
import type { GroomingAction, GroomingSchedule } from '$lib/types';
let { data, form }: { data: PageData; form: ActionData } = $props();
let { schedule } = $derived(data);
let showAddForm = $state(false);
let editingId = $state<string | null>(null);
const DAY_NAMES = $derived([
m["grooming_dayMonday"](),
m["grooming_dayTuesday"](),
m["grooming_dayWednesday"](),
m["grooming_dayThursday"](),
m["grooming_dayFriday"](),
m["grooming_daySaturday"](),
m["grooming_daySunday"]()
]);
const ACTION_LABELS = $derived({
shaving_razor: m["grooming_actionShavingRazor"](),
shaving_oneblade: m["grooming_actionShavingOneblade"](),
dermarolling: m["grooming_actionDermarolling"]()
} as Record<GroomingAction, string>);
const ALL_ACTIONS: GroomingAction[] = ['shaving_razor', 'shaving_oneblade', 'dermarolling'];
const byDay = $derived(
DAY_NAMES.map((name, idx) => ({
name,
day: idx,
entries: schedule.filter((e: GroomingSchedule) => e.day_of_week === idx)
})).filter((d) => d.entries.length > 0)
);
</script>
<svelte:head><title>{m.grooming_title()} — innercontext</title></svelte:head>
<div class="editorial-page space-y-4">
<section class="editorial-hero reveal-1 space-y-3">
<a href={resolve('/routines')} class="editorial-backlink"><ArrowLeft class="size-4" /> {m["grooming_backToRoutines"]()}</a>
<p class="editorial-kicker">{m["nav_appSubtitle"]()}</p>
<h2 class="editorial-title text-[clamp(1.8rem,3vw,2.4rem)]">{m.grooming_title()}</h2>
<div class="editorial-toolbar">
<Button variant="outline" size="sm" onclick={() => (showAddForm = !showAddForm)}>
{showAddForm ? m.common_cancel() : m["grooming_addEntry"]()}
</Button>
</div>
</section>
{#if form?.error}
<div class="editorial-alert editorial-alert--error">{form.error}</div>
{/if}
{#if form?.created}
<div class="editorial-alert editorial-alert--success">{m["grooming_entryAdded"]()}</div>
{/if}
{#if form?.updated}
<div class="editorial-alert editorial-alert--success">{m["grooming_entryUpdated"]()}</div>
{/if}
{#if form?.deleted}
<div class="editorial-alert editorial-alert--success">{m["grooming_entryDeleted"]()}</div>
{/if}
<!-- Add form -->
{#if showAddForm}
<Card>
<CardContent class="pt-4">
<form
method="POST"
action="?/create"
use:enhance={() => {
return async ({ update }) => {
await update();
showAddForm = false;
};
}}
class="grid grid-cols-2 gap-4"
>
<div class="space-y-1">
<Label for="add_day">{m["grooming_dayOfWeek"]()}</Label>
<select
id="add_day"
name="day_of_week"
required
class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
>
{#each DAY_NAMES as name, idx (idx)}
<option value={idx}>{name}</option>
{/each}
</select>
</div>
<div class="space-y-1">
<Label for="add_action">{m.grooming_action()}</Label>
<select
id="add_action"
name="action"
required
class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
>
{#each ALL_ACTIONS as action (action)}
<option value={action}>{ACTION_LABELS[action]}</option>
{/each}
</select>
</div>
<div class="col-span-2 space-y-1">
<Label for="add_notes">{m["grooming_notesOptional"]()}</Label>
<Input id="add_notes" name="notes" placeholder={m["grooming_notesPlaceholder"]()} />
</div>
<div class="col-span-2 flex gap-2">
<Button type="submit" size="sm">{m.common_add()}</Button>
<Button type="button" variant="ghost" size="sm" onclick={() => (showAddForm = false)}>
{m.common_cancel()}
</Button>
</div>
</form>
</CardContent>
</Card>
{/if}
<!-- Entries grouped by day -->
{#if schedule.length === 0}
<p class="text-sm text-muted-foreground">{m["grooming_noEntries"]()}</p>
{:else}
<div class="space-y-4">
{#each byDay as { name, entries, day } (day)}
<div class="space-y-2">
<h3 class="text-sm font-semibold uppercase tracking-wide text-muted-foreground">{name}</h3>
{#each entries as entry (entry.id)}
<div class="rounded-md border border-border text-sm">
<!-- Entry row -->
<div class="flex items-center justify-between px-4 py-3">
<div class="flex flex-wrap items-center gap-2">
<Badge variant="secondary">{ACTION_LABELS[entry.action]}</Badge>
{#if entry.notes}
<span class="text-muted-foreground">{entry.notes}</span>
{/if}
</div>
<div class="flex items-center gap-1">
<Button
variant="ghost"
size="sm"
onclick={() => (editingId = editingId === entry.id ? null : entry.id)}
>
{editingId === entry.id ? m.common_cancel() : m.common_edit()}
</Button>
<form
method="POST"
action="?/delete"
use:enhance
onsubmit={(e) => {
if (!confirm(m["grooming_confirmDelete"]())) e.preventDefault();
}}
>
<input type="hidden" name="id" value={entry.id} />
<Button variant="ghost" size="sm" type="submit" class="text-destructive hover:text-destructive">
{m.common_delete()}
</Button>
</form>
</div>
</div>
<!-- Inline edit form -->
{#if editingId === entry.id}
<div class="border-t border-border bg-muted/30 px-4 py-3">
<form
method="POST"
action="?/update"
use:enhance={() => {
return async ({ update }) => {
await update();
editingId = null;
};
}}
class="grid grid-cols-2 gap-3"
>
<input type="hidden" name="id" value={entry.id} />
<div class="space-y-1">
<Label>{m["grooming_dayOfWeek"]()}</Label>
<select
name="day_of_week"
class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
>
{#each DAY_NAMES as dayName, idx (idx)}
<option value={idx} selected={entry.day_of_week === idx}>{dayName}</option>
{/each}
</select>
</div>
<div class="space-y-1">
<Label>{m.grooming_action()}</Label>
<select
name="action"
class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
>
{#each ALL_ACTIONS as a (a)}
<option value={a} selected={entry.action === a}>{ACTION_LABELS[a]}</option>
{/each}
</select>
</div>
<div class="col-span-2 space-y-1">
<Label>{m.inventory_notes()}</Label>
<Input name="notes" value={entry.notes ?? ''} placeholder={m["common_optional_notes"]()} />
</div>
<div class="col-span-2 flex gap-2">
<Button type="submit" size="sm">{m.common_save()}</Button>
<Button type="button" variant="ghost" size="sm" onclick={() => (editingId = null)}>
{m.common_cancel()}
</Button>
</div>
</form>
</div>
{/if}
</div>
{/each}
</div>
<Separator />
{/each}
</div>
{/if}
</div>