feat(frontend): group product selector by category in routine step forms
Products in the Add/Edit step dropdowns are now grouped by category (Cleanser → Serum → Moisturizer → …) using SelectGroup/SelectGroupHeading, and sorted alphabetically by brand then name within each group. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
78c67b6179
commit
3aa03b412b
1 changed files with 50 additions and 5 deletions
|
|
@ -10,7 +10,14 @@
|
|||
import { Card, CardContent, CardHeader, CardTitle } from '$lib/components/ui/card';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/components/ui/select';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectGroupHeading,
|
||||
SelectItem,
|
||||
SelectTrigger
|
||||
} from '$lib/components/ui/select';
|
||||
import { Separator } from '$lib/components/ui/separator';
|
||||
|
||||
let { data, form }: { data: PageData; form: ActionData } = $props();
|
||||
|
|
@ -103,6 +110,34 @@
|
|||
let selectedProductId = $state('');
|
||||
|
||||
const GROOMING_ACTIONS: GroomingAction[] = ['shaving_razor', 'shaving_oneblade', 'dermarolling'];
|
||||
|
||||
const CATEGORY_ORDER = [
|
||||
'cleanser', 'toner', 'essence', 'serum', 'moisturizer',
|
||||
'spf', 'mask', 'exfoliant', 'spot_treatment', 'oil',
|
||||
'hair_treatment', 'tool', 'other'
|
||||
];
|
||||
|
||||
function formatCategory(cat: string): string {
|
||||
return cat.charAt(0).toUpperCase() + cat.slice(1).replace(/_/g, ' ');
|
||||
}
|
||||
|
||||
const groupedProducts = $derived.by(() => {
|
||||
const groups = new Map<string, typeof products>();
|
||||
for (const p of products) {
|
||||
const key = p.category ?? 'other';
|
||||
if (!groups.has(key)) groups.set(key, []);
|
||||
groups.get(key)!.push(p);
|
||||
}
|
||||
for (const list of groups.values()) {
|
||||
list.sort((a, b) => {
|
||||
const b_cmp = (a.brand ?? '').localeCompare(b.brand ?? '');
|
||||
return b_cmp !== 0 ? b_cmp : a.name.localeCompare(b.name);
|
||||
});
|
||||
}
|
||||
return CATEGORY_ORDER
|
||||
.filter((c) => groups.has(c))
|
||||
.map((c) => [c, groups.get(c)!] as const);
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head><title>Routine {routine.routine_date} {routine.part_of_day.toUpperCase()} — innercontext</title></svelte:head>
|
||||
|
|
@ -150,8 +185,13 @@
|
|||
{/if}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{#each products as p (p.id)}
|
||||
<SelectItem value={p.id}>{p.name} ({p.brand})</SelectItem>
|
||||
{#each groupedProducts as [cat, items]}
|
||||
<SelectGroup>
|
||||
<SelectGroupHeading>{formatCategory(cat)}</SelectGroupHeading>
|
||||
{#each items as p (p.id)}
|
||||
<SelectItem value={p.id}>{p.name} · {p.brand}</SelectItem>
|
||||
{/each}
|
||||
</SelectGroup>
|
||||
{/each}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
|
@ -202,8 +242,13 @@
|
|||
{/if}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{#each products as p (p.id)}
|
||||
<SelectItem value={p.id}>{p.name} ({p.brand})</SelectItem>
|
||||
{#each groupedProducts as [cat, items]}
|
||||
<SelectGroup>
|
||||
<SelectGroupHeading>{formatCategory(cat)}</SelectGroupHeading>
|
||||
{#each items as p (p.id)}
|
||||
<SelectItem value={p.id}>{p.name} · {p.brand}</SelectItem>
|
||||
{/each}
|
||||
</SelectGroup>
|
||||
{/each}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue