From d938c9999b977efed2027583cdb20439b458fe90 Mon Sep 17 00:00:00 2001 From: Piotr Oleszczyk Date: Sat, 28 Feb 2026 21:37:15 +0100 Subject: [PATCH] feat(frontend): add edit and delete for skin snapshots Add inline edit form and delete button to each snapshot card on /skin. Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/routes/skin/+page.server.ts | 56 ++++- frontend/src/routes/skin/+page.svelte | 294 ++++++++++++++++++++--- 2 files changed, 309 insertions(+), 41 deletions(-) diff --git a/frontend/src/routes/skin/+page.server.ts b/frontend/src/routes/skin/+page.server.ts index f11c131..95399cd 100644 --- a/frontend/src/routes/skin/+page.server.ts +++ b/frontend/src/routes/skin/+page.server.ts @@ -1,4 +1,4 @@ -import { createSkinSnapshot, getSkinSnapshots } from '$lib/api'; +import { createSkinSnapshot, deleteSkinSnapshot, getSkinSnapshots, updateSkinSnapshot } from '$lib/api'; import { fail } from '@sveltejs/kit'; import type { Actions, PageServerLoad } from './$types'; @@ -50,5 +50,59 @@ export const actions: Actions = { } catch (e) { return fail(500, { error: (e as Error).message }); } + }, + + update: async ({ request }) => { + const form = await request.formData(); + const id = form.get('id') as string; + const snapshot_date = form.get('snapshot_date') as string; + const overall_state = form.get('overall_state') as string; + const texture = form.get('texture') as string; + const notes = form.get('notes') as string; + const hydration_level = form.get('hydration_level') as string; + const sensitivity_level = form.get('sensitivity_level') as string; + const barrier_state = form.get('barrier_state') as string; + const active_concerns_raw = form.get('active_concerns') as string; + const skin_type = form.get('skin_type') as string; + const sebum_tzone = form.get('sebum_tzone') as string; + const sebum_cheeks = form.get('sebum_cheeks') as string; + + if (!id) return fail(400, { error: 'Missing id' }); + + const active_concerns = active_concerns_raw + ?.split(',') + .map((c) => c.trim()) + .filter(Boolean) ?? []; + + const body: Record = { active_concerns }; + if (snapshot_date) body.snapshot_date = snapshot_date; + if (overall_state) body.overall_state = overall_state; + if (texture) body.texture = texture; + if (notes) body.notes = notes; + if (hydration_level) body.hydration_level = Number(hydration_level); + if (sensitivity_level) body.sensitivity_level = Number(sensitivity_level); + if (barrier_state) body.barrier_state = barrier_state; + if (skin_type) body.skin_type = skin_type; + if (sebum_tzone) body.sebum_tzone = Number(sebum_tzone); + if (sebum_cheeks) body.sebum_cheeks = Number(sebum_cheeks); + + try { + await updateSkinSnapshot(id, body); + return { updated: true }; + } catch (e) { + return fail(500, { error: (e as Error).message }); + } + }, + + delete: async ({ request }) => { + const form = await request.formData(); + const id = form.get('id') as string; + if (!id) return fail(400, { error: 'Missing id' }); + try { + await deleteSkinSnapshot(id); + return { deleted: true }; + } catch (e) { + return fail(500, { error: (e as Error).message }); + } } }; diff --git a/frontend/src/routes/skin/+page.svelte b/frontend/src/routes/skin/+page.svelte index 14b5777..9f13c18 100644 --- a/frontend/src/routes/skin/+page.svelte +++ b/frontend/src/routes/skin/+page.svelte @@ -25,7 +25,7 @@ let showForm = $state(false); - // Form state (bound to inputs so AI can pre-fill) + // Create form state (bound to inputs so AI can pre-fill) let snapshotDate = $state(new Date().toISOString().slice(0, 10)); let overallState = $state(''); let texture = $state(''); @@ -38,6 +38,36 @@ let activeConcernsRaw = $state(''); let notes = $state(''); + // Edit state + let editingId = $state(null); + let editSnapshotDate = $state(''); + let editOverallState = $state(''); + let editTexture = $state(''); + let editBarrierState = $state(''); + let editSkinType = $state(''); + let editHydrationLevel = $state(''); + let editSensitivityLevel = $state(''); + let editSebumTzone = $state(''); + let editSebumCheeks = $state(''); + let editActiveConcernsRaw = $state(''); + let editNotes = $state(''); + + function startEdit(snap: (typeof data.snapshots)[number]) { + editingId = snap.id; + editSnapshotDate = snap.snapshot_date; + editOverallState = snap.overall_state ?? ''; + editTexture = snap.texture ?? ''; + editBarrierState = snap.barrier_state ?? ''; + editSkinType = snap.skin_type ?? ''; + editHydrationLevel = snap.hydration_level != null ? String(snap.hydration_level) : ''; + editSensitivityLevel = snap.sensitivity_level != null ? String(snap.sensitivity_level) : ''; + editSebumTzone = snap.sebum_tzone != null ? String(snap.sebum_tzone) : ''; + editSebumCheeks = snap.sebum_cheeks != null ? String(snap.sebum_cheeks) : ''; + editActiveConcernsRaw = snap.active_concerns?.join(', ') ?? ''; + editNotes = snap.notes ?? ''; + showForm = false; + } + // AI photo analysis state let aiPanelOpen = $state(false); let selectedFiles = $state([]); @@ -101,6 +131,12 @@ {#if form?.created}
Snapshot added.
{/if} + {#if form?.updated} +
Snapshot updated.
+ {/if} + {#if form?.deleted} +
Snapshot deleted.
+ {/if} {#if showForm} @@ -284,52 +320,230 @@ {#each sortedSnapshots as snap (snap.id)} -
- {snap.snapshot_date} -
- {#if snap.overall_state} - +
async ({ result, update }) => { + await update(); + if (result.type === 'success') editingId = null; + }} + class="grid grid-cols-2 gap-4" + > + +
+ + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ {:else} + +
+ {snap.snapshot_date} +
+ {#if snap.overall_state} + + {snap.overall_state} + + {/if} + {#if snap.texture} + {snap.texture} + {/if} + +
+ + +
+
+
+
+ {#if snap.hydration_level != null} +
+

Hydration

+

{snap.hydration_level}/5

+
{/if} - {#if snap.texture} - {snap.texture} + {#if snap.sensitivity_level != null} +
+

Sensitivity

+

{snap.sensitivity_level}/5

+
+ {/if} + {#if snap.barrier_state} +
+

Barrier

+

{snap.barrier_state.replace(/_/g, ' ')}

+
{/if}
-
-
- {#if snap.hydration_level != null} -
-

Hydration

-

{snap.hydration_level}/5

+ {#if snap.active_concerns.length} +
+ {#each snap.active_concerns as c (c)} + {c.replace(/_/g, ' ')} + {/each}
{/if} - {#if snap.sensitivity_level != null} -
-

Sensitivity

-

{snap.sensitivity_level}/5

-
+ {#if snap.notes} +

{snap.notes}

{/if} - {#if snap.barrier_state} -
-

Barrier

-

{snap.barrier_state.replace(/_/g, ' ')}

-
- {/if} -
- {#if snap.active_concerns.length} -
- {#each snap.active_concerns as c (c)} - {c.replace(/_/g, ' ')} - {/each} -
- {/if} - {#if snap.notes} -

{snap.notes}

{/if}