feat(frontend): responsive design for mobile (RWD)
- Layout: mobile hamburger + drawer nav (backdrop button + sibling nav), desktop sidebar hidden on small screens, p-4 md:p-8 main padding - Products: card list view on mobile, flex-wrap filters - Lab results: card list view on mobile - ProductForm: responsive grids (grid-cols-1 sm:grid-cols-2), skin profile checkboxes 2→3 cols, active ingredient row restructured (name+✕ in flex row, percent/strength/irritation in 3-col grid), section headers stack on mobile - Skin snapshots: date+icons on one row, badges on separate row below - Product [id] header: back link stacked above title, redundant badge removed - Routines header: flex-col on mobile, sm:flex-row Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c85ca355df
commit
679e4e81f4
8 changed files with 193 additions and 60 deletions
|
|
@ -451,7 +451,7 @@
|
|||
<Card>
|
||||
<CardHeader><CardTitle>{m["productForm_basicInfo"]()}</CardTitle></CardHeader>
|
||||
<CardContent class="space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="name">{m["productForm_name"]()}</Label>
|
||||
<Input id="name" name="name" required placeholder={m["productForm_namePlaceholder"]()} bind:value={name} />
|
||||
|
|
@ -461,7 +461,7 @@
|
|||
<Input id="brand" name="brand" required placeholder={m["productForm_brandPlaceholder"]()} bind:value={brand} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="line_name">{m["productForm_lineName"]()}</Label>
|
||||
<Input id="line_name" name="line_name" placeholder={m["productForm_lineNamePlaceholder"]()} bind:value={lineName} />
|
||||
|
|
@ -471,7 +471,7 @@
|
|||
<Input id="url" name="url" type="url" placeholder="https://…" bind:value={url} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="sku">{m["productForm_sku"]()}</Label>
|
||||
<Input id="sku" name="sku" placeholder={m["productForm_skuPlaceholder"]()} bind:value={sku} />
|
||||
|
|
@ -488,7 +488,7 @@
|
|||
<Card>
|
||||
<CardHeader><CardTitle>{m["productForm_classification"]()}</CardTitle></CardHeader>
|
||||
<CardContent>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div class="col-span-2 space-y-2">
|
||||
<Label>{m["productForm_category"]()}</Label>
|
||||
<input type="hidden" name="category" value={category} />
|
||||
|
|
@ -564,7 +564,7 @@
|
|||
<CardContent class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label>{m["productForm_recommendedFor"]()}</Label>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div class="grid grid-cols-2 gap-2 sm:grid-cols-3">
|
||||
{#each skinTypes as st}
|
||||
<label class="flex cursor-pointer items-center gap-2 text-sm">
|
||||
<input
|
||||
|
|
@ -588,7 +588,7 @@
|
|||
|
||||
<div class="space-y-2">
|
||||
<Label>{m["productForm_targetConcerns"]()}</Label>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div class="grid grid-cols-2 gap-2 sm:grid-cols-3">
|
||||
{#each skinConcerns as sc}
|
||||
<label class="flex cursor-pointer items-center gap-2 text-sm">
|
||||
<input
|
||||
|
|
@ -641,7 +641,7 @@
|
|||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<Label>{m["productForm_activeIngredients"]()}</Label>
|
||||
<Button type="button" variant="outline" size="sm" onclick={addActive}>{m["productForm_addActive"]()}</Button>
|
||||
</div>
|
||||
|
|
@ -650,14 +650,23 @@
|
|||
|
||||
{#each actives as active, i}
|
||||
<div class="rounded-md border border-border p-3 space-y-3">
|
||||
<div class="grid grid-cols-[1fr_100px_120px_120px_auto] gap-2 items-end">
|
||||
<div class="space-y-1">
|
||||
<div class="flex items-end gap-2">
|
||||
<div class="min-w-0 flex-1 space-y-1">
|
||||
<Label class="text-xs">{m["productForm_activeName"]()}</Label>
|
||||
<Input
|
||||
placeholder="e.g. Niacinamide"
|
||||
bind:value={active.name}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onclick={() => removeActive(i)}
|
||||
class="h-7 w-7 shrink-0 p-0 text-destructive hover:text-destructive"
|
||||
>✕</Button>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div class="space-y-1">
|
||||
<Label class="text-xs">{m["productForm_activePercent"]()}</Label>
|
||||
<Input
|
||||
|
|
@ -687,18 +696,11 @@
|
|||
<option value="3">{m["productForm_strengthHigh"]()}</option>
|
||||
</select>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onclick={() => removeActive(i)}
|
||||
class="text-destructive hover:text-destructive"
|
||||
>✕</Button>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<Label class="text-xs text-muted-foreground">{m["productForm_activeFunctions"]()}</Label>
|
||||
<div class="grid grid-cols-4 gap-1">
|
||||
<div class="grid grid-cols-2 gap-1 sm:grid-cols-4">
|
||||
{#each ingFunctions as fn}
|
||||
<label class="flex cursor-pointer items-center gap-1.5 text-xs">
|
||||
<input
|
||||
|
|
@ -764,7 +766,7 @@
|
|||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<Label>{m["productForm_incompatibleWith"]()}</Label>
|
||||
<Button type="button" variant="outline" size="sm" onclick={addIncompatible}>
|
||||
{m["productForm_addIncompatibility"]()}
|
||||
|
|
@ -774,7 +776,7 @@
|
|||
<input type="hidden" name="incompatible_with_json" value={incompatibleJson} />
|
||||
|
||||
{#each incompatibleWith as row, i}
|
||||
<div class="grid grid-cols-[1fr_140px_1fr_auto] gap-2 items-end">
|
||||
<div class="grid grid-cols-2 gap-2 items-end sm:grid-cols-[1fr_140px_1fr_auto]">
|
||||
<div class="space-y-1">
|
||||
<Label class="text-xs">{m["productForm_incompTarget"]()}</Label>
|
||||
<Input placeholder="e.g. Vitamin C" bind:value={row.target} />
|
||||
|
|
@ -813,7 +815,7 @@
|
|||
<Card>
|
||||
<CardHeader><CardTitle>{m["productForm_contextRules"]()}</CardTitle></CardHeader>
|
||||
<CardContent>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label>{m["productForm_ctxAfterShaving"]()}</Label>
|
||||
<input type="hidden" name="ctx_safe_after_shaving" value={ctxAfterShaving} />
|
||||
|
|
@ -884,7 +886,7 @@
|
|||
<Card>
|
||||
<CardHeader><CardTitle>{m["productForm_productDetails"]()}</CardTitle></CardHeader>
|
||||
<CardContent class="space-y-4">
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div class="grid grid-cols-2 gap-4 sm:grid-cols-3">
|
||||
<div class="space-y-2">
|
||||
<Label>{m["productForm_priceTier"]()}</Label>
|
||||
<input type="hidden" name="price_tier" value={priceTier} />
|
||||
|
|
@ -919,7 +921,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="ph_min">{m["productForm_phMin"]()}</Label>
|
||||
<Input id="ph_min" name="ph_min" type="number" min="0" max="14" step="0.1" placeholder="e.g. 3.5" bind:value={phMin} />
|
||||
|
|
@ -948,7 +950,7 @@
|
|||
<Card>
|
||||
<CardHeader><CardTitle>{m["productForm_safetyFlags"]()}</CardTitle></CardHeader>
|
||||
<CardContent>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label>{m["productForm_fragranceFree"]()}</Label>
|
||||
<input type="hidden" name="fragrance_free" value={fragranceFree} />
|
||||
|
|
@ -1008,7 +1010,7 @@
|
|||
<Card>
|
||||
<CardHeader><CardTitle>{m["productForm_usageConstraints"]()}</CardTitle></CardHeader>
|
||||
<CardContent class="space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="min_interval_hours">{m["productForm_minIntervalHours"]()}</Label>
|
||||
<Input id="min_interval_hours" name="min_interval_hours" type="number" min="0" placeholder="e.g. 24" bind:value={minIntervalHours} />
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
let { children } = $props();
|
||||
|
||||
let mobileMenuOpen = $state(false);
|
||||
|
||||
const navItems = $derived([
|
||||
{ href: '/', label: m.nav_dashboard(), icon: '🏠' },
|
||||
{ href: '/products', label: m.nav_products(), icon: '🧴' },
|
||||
|
|
@ -28,9 +30,68 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="flex min-h-screen bg-background">
|
||||
<!-- Sidebar -->
|
||||
<nav class="w-56 shrink-0 border-r border-border bg-card px-3 py-6">
|
||||
<div class="flex min-h-screen flex-col bg-background md:flex-row">
|
||||
<!-- Mobile header -->
|
||||
<header class="flex items-center justify-between border-b border-border bg-card px-4 py-3 md:hidden">
|
||||
<div>
|
||||
<span class="text-sm font-semibold tracking-tight">{m["nav_appName"]()}</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (mobileMenuOpen = !mobileMenuOpen)}
|
||||
class="flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
||||
aria-label="Toggle menu"
|
||||
>
|
||||
{#if mobileMenuOpen}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||
{:else}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
||||
{/if}
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<!-- Mobile drawer overlay -->
|
||||
{#if mobileMenuOpen}
|
||||
<!-- Backdrop: closes drawer on click -->
|
||||
<button
|
||||
type="button"
|
||||
class="fixed inset-0 z-50 bg-black/50 md:hidden"
|
||||
onclick={() => (mobileMenuOpen = false)}
|
||||
aria-label={m.common_cancel()}
|
||||
></button>
|
||||
<!-- Drawer (same z-50 but later in DOM → on top of backdrop) -->
|
||||
<nav
|
||||
class="fixed inset-y-0 left-0 z-50 w-64 overflow-y-auto bg-card px-3 py-6 md:hidden"
|
||||
>
|
||||
<div class="mb-8 px-3">
|
||||
<h1 class="text-lg font-semibold tracking-tight">{m["nav_appName"]()}</h1>
|
||||
<p class="text-xs text-muted-foreground">{m["nav_appSubtitle"]()}</p>
|
||||
</div>
|
||||
<ul class="space-y-1">
|
||||
{#each navItems as item}
|
||||
<li>
|
||||
<a
|
||||
href={item.href}
|
||||
onclick={() => (mobileMenuOpen = false)}
|
||||
class="flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors
|
||||
{isActive(item.href)
|
||||
? 'bg-accent text-accent-foreground font-medium'
|
||||
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'}"
|
||||
>
|
||||
<span class="text-base">{item.icon}</span>
|
||||
{item.label}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<div class="mt-6 px-3">
|
||||
<LanguageSwitcher />
|
||||
</div>
|
||||
</nav>
|
||||
{/if}
|
||||
|
||||
<!-- Desktop Sidebar -->
|
||||
<nav class="hidden w-56 shrink-0 flex-col border-r border-border bg-card px-3 py-6 md:flex">
|
||||
<div class="mb-8 px-3">
|
||||
<h1 class="text-lg font-semibold tracking-tight">{m["nav_appName"]()}</h1>
|
||||
<p class="text-xs text-muted-foreground">{m["nav_appSubtitle"]()}</p>
|
||||
|
|
@ -57,7 +118,7 @@
|
|||
</nav>
|
||||
|
||||
<!-- Main content -->
|
||||
<main class="flex-1 overflow-auto p-8">
|
||||
<main class="flex-1 overflow-auto p-4 md:p-8">
|
||||
{@render children()}
|
||||
</main>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@
|
|||
<Card>
|
||||
<CardHeader><CardTitle>{m["labResults_newTitle"]()}</CardTitle></CardHeader>
|
||||
<CardContent>
|
||||
<form method="POST" action="?/create" use:enhance class="grid grid-cols-2 gap-4">
|
||||
<form method="POST" action="?/create" use:enhance class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div class="space-y-1">
|
||||
<Label for="collected_at">{m["labResults_date"]()}</Label>
|
||||
<Input id="collected_at" name="collected_at" type="date" required />
|
||||
|
|
@ -125,7 +125,8 @@
|
|||
</Card>
|
||||
{/if}
|
||||
|
||||
<div class="rounded-md border border-border">
|
||||
<!-- Desktop: table -->
|
||||
<div class="hidden rounded-md border border-border md:block">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
|
|
@ -173,4 +174,34 @@
|
|||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<!-- Mobile: cards -->
|
||||
<div class="flex flex-col gap-3 md:hidden">
|
||||
{#each data.results as r (r.record_id)}
|
||||
<div class="rounded-lg border border-border p-4 flex flex-col gap-1">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<span class="font-medium">{r.test_name_original ?? r.test_code}</span>
|
||||
{#if r.flag}
|
||||
<span class="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium {flagColors[r.flag] ?? ''}">
|
||||
{r.flag}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground">{r.collected_at.slice(0, 10)}</p>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<span class="font-mono text-xs text-muted-foreground">{r.test_code}</span>
|
||||
{#if r.value_num != null}
|
||||
<span>{r.value_num} {r.unit_original ?? ''}</span>
|
||||
{:else if r.value_text}
|
||||
<span>{r.value_text}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if r.lab}
|
||||
<p class="text-xs text-muted-foreground">{r.lab}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<p class="py-8 text-center text-sm text-muted-foreground">{m["labResults_noResults"]()}</p>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@
|
|||
<Card>
|
||||
<CardHeader><CardTitle>{m["medications_newTitle"]()}</CardTitle></CardHeader>
|
||||
<CardContent>
|
||||
<form method="POST" action="?/create" use:enhance class="grid grid-cols-2 gap-4">
|
||||
<form method="POST" action="?/create" use:enhance class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div class="space-y-1 col-span-2">
|
||||
<Label>{m.medications_kind()}</Label>
|
||||
<input type="hidden" name="kind" value={kind} />
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@
|
|||
<Button href="/products/new">{m["products_addNew"]()}</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-1">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{#each (['all', 'owned', 'unowned'] as OwnershipFilter[]) as f (f)}
|
||||
<Button
|
||||
variant={ownershipFilter === f ? 'default' : 'outline'}
|
||||
|
|
@ -78,7 +78,8 @@
|
|||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border border-border">
|
||||
<!-- Desktop: table -->
|
||||
<div class="hidden rounded-md border border-border md:block">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
|
|
@ -128,4 +129,41 @@
|
|||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<!-- Mobile: cards -->
|
||||
<div class="flex flex-col gap-3 md:hidden">
|
||||
{#if totalCount === 0}
|
||||
<p class="py-8 text-center text-sm text-muted-foreground">{m["products_noProducts"]()}</p>
|
||||
{:else}
|
||||
{#each groupedProducts as [category, products] (category)}
|
||||
<div class="border-b border-border pb-1 pt-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
{category.replace(/_/g, ' ')}
|
||||
</div>
|
||||
{#each products as product (product.id)}
|
||||
<a
|
||||
href="/products/{product.id}"
|
||||
class="block rounded-lg border border-border p-4 hover:bg-muted/50"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div>
|
||||
<p class="font-medium">{product.name}</p>
|
||||
<p class="text-sm text-muted-foreground">{product.brand}</p>
|
||||
</div>
|
||||
<span class="shrink-0 text-xs uppercase text-muted-foreground">{product.recommended_time}</span>
|
||||
</div>
|
||||
{#if product.targets.length}
|
||||
<div class="mt-2 flex flex-wrap gap-1">
|
||||
{#each product.targets.slice(0, 3) as t (t)}
|
||||
<Badge variant="secondary" class="text-xs">{t.replace(/_/g, ' ')}</Badge>
|
||||
{/each}
|
||||
{#if product.targets.length > 3}
|
||||
<span class="text-xs text-muted-foreground">+{product.targets.length - 3}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</a>
|
||||
{/each}
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -20,10 +20,9 @@
|
|||
<svelte:head><title>{product.name} — innercontext</title></svelte:head>
|
||||
|
||||
<div class="max-w-2xl space-y-6">
|
||||
<div class="flex items-center gap-4">
|
||||
<div>
|
||||
<a href="/products" class="text-sm text-muted-foreground hover:underline">{m["products_backToList"]()}</a>
|
||||
<h2 class="text-2xl font-bold tracking-tight">{product.name}</h2>
|
||||
<Badge variant="outline">{product.category.replace(/_/g, ' ')}</Badge>
|
||||
<h2 class="mt-1 text-2xl font-bold tracking-tight">{product.name}</h2>
|
||||
</div>
|
||||
|
||||
{#if form?.error}
|
||||
|
|
|
|||
|
|
@ -22,12 +22,12 @@
|
|||
<svelte:head><title>{m.routines_title()} — innercontext</title></svelte:head>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold tracking-tight">{m.routines_title()}</h2>
|
||||
<p class="text-muted-foreground">{m.routines_count({ count: data.routines.length })}</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Button href="/routines/suggest" variant="outline">{m["routines_suggestAI"]()}</Button>
|
||||
<Button href="/routines/new">{m["routines_addNew"]()}</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@
|
|||
<Card>
|
||||
<CardHeader><CardTitle>{m["skin_newSnapshotTitle"]()}</CardTitle></CardHeader>
|
||||
<CardContent>
|
||||
<form method="POST" action="?/create" use:enhance class="grid grid-cols-2 gap-4">
|
||||
<form method="POST" action="?/create" use:enhance class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div class="space-y-1">
|
||||
<Label for="snapshot_date">{m.skin_date()}</Label>
|
||||
<Input
|
||||
|
|
@ -332,7 +332,7 @@
|
|||
await update();
|
||||
if (result.type === 'success') editingId = null;
|
||||
}}
|
||||
class="grid grid-cols-2 gap-4"
|
||||
class="grid grid-cols-1 sm:grid-cols-2 gap-4"
|
||||
>
|
||||
<input type="hidden" name="id" value={snap.id} />
|
||||
<div class="space-y-1">
|
||||
|
|
@ -422,27 +422,29 @@
|
|||
</form>
|
||||
{:else}
|
||||
<!-- Read view -->
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<span class="font-medium">{snap.snapshot_date}</span>
|
||||
<div class="flex items-center gap-2">
|
||||
{#if snap.overall_state}
|
||||
<span class="rounded-full px-2 py-0.5 text-xs font-medium {stateColors[snap.overall_state] ?? ''}">
|
||||
{stateLabels[snap.overall_state]?.() ?? snap.overall_state}
|
||||
</span>
|
||||
{/if}
|
||||
{#if snap.texture}
|
||||
<Badge variant="secondary">{textureLabels[snap.texture]?.() ?? snap.texture}</Badge>
|
||||
{/if}
|
||||
<Button variant="ghost" size="sm" onclick={() => startEdit(snap)} class="h-7 px-2 text-xs">
|
||||
{m.common_edit()}
|
||||
</Button>
|
||||
<form method="POST" action="?/delete" use:enhance>
|
||||
<input type="hidden" name="id" value={snap.id} />
|
||||
<Button type="submit" variant="ghost" size="sm" class="h-7 px-2 text-xs text-destructive hover:text-destructive">
|
||||
{m.common_delete()}
|
||||
</Button>
|
||||
</form>
|
||||
<div class="mb-3 space-y-1.5">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-medium">{snap.snapshot_date}</span>
|
||||
<div class="flex items-center gap-1">
|
||||
<Button variant="ghost" size="sm" onclick={() => startEdit(snap)} class="h-7 w-7 shrink-0 p-0 text-muted-foreground hover:text-foreground" aria-label={m.common_edit()}>✎</Button>
|
||||
<form method="POST" action="?/delete" use:enhance>
|
||||
<input type="hidden" name="id" value={snap.id} />
|
||||
<Button type="submit" variant="ghost" size="sm" class="h-7 w-7 shrink-0 p-0 text-destructive hover:text-destructive" aria-label={m.common_delete()}>×</Button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{#if snap.overall_state || snap.texture}
|
||||
<div class="flex flex-wrap items-center gap-1.5">
|
||||
{#if snap.overall_state}
|
||||
<span class="rounded-full px-2 py-0.5 text-xs font-medium {stateColors[snap.overall_state] ?? ''}">
|
||||
{stateLabels[snap.overall_state]?.() ?? snap.overall_state}
|
||||
</span>
|
||||
{/if}
|
||||
{#if snap.texture}
|
||||
<Badge variant="secondary">{textureLabels[snap.texture]?.() ?? snap.texture}</Badge>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-3 text-sm mb-3">
|
||||
{#if snap.hydration_level != null}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue