From 37573109a05a07ac50827759ea63793209baa73a Mon Sep 17 00:00:00 2001 From: Martin Kagamino Lehoux Date: Mon, 7 Sep 2026 19:17:45 +0200 Subject: Add routine detail page with editable description and health history --- src/routes/projects/[id]/+page.server.ts | 7 +- src/routes/projects/[id]/+page.svelte | 12 +- .../routine/[routineId]/+page.server.ts | 80 ++++ .../[projectId]/routine/[routineId]/+page.svelte | 440 +++++++++++++++++++++ 4 files changed, 534 insertions(+), 5 deletions(-) create mode 100644 src/routes/projects/[projectId]/routine/[routineId]/+page.server.ts create mode 100644 src/routes/projects/[projectId]/routine/[routineId]/+page.svelte (limited to 'src/routes/projects') diff --git a/src/routes/projects/[id]/+page.server.ts b/src/routes/projects/[id]/+page.server.ts index b20af6c..e3c4edf 100644 --- a/src/routes/projects/[id]/+page.server.ts +++ b/src/routes/projects/[id]/+page.server.ts @@ -60,11 +60,10 @@ export const actions: Actions = { return fail(400, { error: 'Le nom est requis' }); } try { - await edit( - numericId(data.get('id')), + await edit(numericId(data.get('id')), { name, - parseFrequencyDays(data.get('frequencyDays')), - ); + frequencyDays: parseFrequencyDays(data.get('frequencyDays')), + }); } catch { return fail(400, { error: 'Modification invalide' }); } diff --git a/src/routes/projects/[id]/+page.svelte b/src/routes/projects/[id]/+page.svelte index d9747a9..608bbbb 100644 --- a/src/routes/projects/[id]/+page.svelte +++ b/src/routes/projects/[id]/+page.svelte @@ -159,7 +159,11 @@ {:else}
- {routine.name} + {routine.name} {formatFrequency(routine.frequencyDays)} {#if routine.lastDoneAt} @@ -229,6 +233,12 @@ .routine-name { font-weight: 500; + color: var(--text); + text-decoration: none; + } + + .routine-name:hover { + color: var(--accent); } .routine-meta { diff --git a/src/routes/projects/[projectId]/routine/[routineId]/+page.server.ts b/src/routes/projects/[projectId]/routine/[routineId]/+page.server.ts new file mode 100644 index 0000000..83bac3c --- /dev/null +++ b/src/routes/projects/[projectId]/routine/[routineId]/+page.server.ts @@ -0,0 +1,80 @@ +import { error, fail } from '@sveltejs/kit'; +import { getById } from '$lib/server/services/projects'; +import { + edit, + complete, + loadCurrent, + loadCompletions, +} from '$lib/server/services/routines'; +import { numericId, parseFormString } from '$lib/server/services/validation'; +import { + parseIntervalToDays, + computeRoutineState, +} from '$lib/shared/routine-metrics'; +import type { Actions, PageServerLoad } from './$types'; + +function parseFrequencyDays(value: FormDataEntryValue | null): number { + const days = parseIntervalToDays(value?.toString() ?? ''); + if (days === null) { + throw new Error('Invalid interval'); + } + return days; +} + +export const load: PageServerLoad = async ({ params }) => { + const projectId = numericId(params.projectId); + const routineId = numericId(params.routineId); + + const project = await getById(projectId); + if (!project || project.archived) { + throw error(404, 'Projet introuvable'); + } + + const routine = await loadCurrent(routineId); + if (!routine || routine.archived || routine.projectId !== projectId) { + throw error(404, 'Routine introuvable'); + } + + const completions = await loadCompletions(routineId); + return { + project: { id: project.id, name: project.name }, + routine, + completions, + state: computeRoutineState(routine, completions, Date.now()), + }; +}; + +export const actions: Actions = { + edit: async ({ request, params }) => { + const data = await request.formData(); + const name = parseFormString(data.get('name')); + if (!name) { + return fail(400, { error: 'Le nom est requis' }); + } + const description = data.get('description')?.toString() ?? ''; + try { + await edit(numericId(params.routineId), { + name, + frequencyDays: parseFrequencyDays(data.get('frequencyDays')), + description, + }); + } catch { + return fail(400, { error: 'Fréquence invalide' }); + } + return { success: true, type: 'edit' as const }; + }, + + complete: async ({ request, params }) => { + const data = await request.formData(); + try { + await complete( + numericId(params.routineId), + parseFormString(data.get('note')), + new Date().toISOString(), + ); + } catch { + return fail(400, { error: 'Routine introuvable' }); + } + return { success: true }; + }, +}; diff --git a/src/routes/projects/[projectId]/routine/[routineId]/+page.svelte b/src/routes/projects/[projectId]/routine/[routineId]/+page.svelte new file mode 100644 index 0000000..ab0774c --- /dev/null +++ b/src/routes/projects/[projectId]/routine/[routineId]/+page.svelte @@ -0,0 +1,440 @@ + + +
+ ← {data.project.name} + + + +
{ + return async ({ result, update }) => { + await update({ reset: false }); + }; + }} + > + {#if form?.error} +

{form.error}

+ {/if} + + + + {#if frequency !== '' && frequencyDays === null} +

Intervalle invalide — ex : 1d, 2w, 3m, 1y

+ {/if} + +

✓ Enregistré

+
+ +

Santé

+
+ {#if geometry} + + {#each geometry.grid as line (line.label)} + + {/each} + + {#each geometry.markers as m (m.x)} + + {/each} + + + {:else} +

Pas encore assez d'historique pour tracer la courbe.

+ {/if} +
+ +

Historique

+
{ + return async ({ update }) => { + note = ''; + await update({ reset: false }); + }; + }} + > + + +
+ +
    + {#each data.completions as c (c.id)} +
  • + + {dateTimeFormatter.format(new Date(c.doneAt))} + + {#if c.note} + {c.note} + {/if} +
  • + {/each} +
+ + {#if data.completions.length === 0} +

Aucune réalisation pour l'instant.

+ {/if} +
+ + -- cgit v1.3