import { useEffect, useMemo, useState } from 'react' import { deleteSleepEntry, fetchSleepDiary, saveSleepEntry, type SleepEntry, type SleepEntryInput, type SleepEvent, type SleepEventType, type SleepQuality } from '../api/sleepDiary' import type { AnalysisPeriod } from '../api/analysis' import { buildSleepDiaryPdf, presentSleepDiaryPdf } from '../report/sleepDiaryPdf' type Language = 'fr' | 'en' const q: readonly SleepQuality[] = ['TB', 'B', 'Moy', 'M', 'TM'] const pointTypes: readonly SleepEventType[] = ['bed_time', 'final_get_up', 'night_get_up', 'daytime_sleepiness'] const copy = { fr: { agenda: 'Agenda', edit: 'Saisie et édition', morning: 'Matin', day: 'Journée / Soir', night: 'Nuit du', bed_time: 'Mise au lit', final_get_up: 'Lever', night_get_up: 'Lever nocturne', sleep: 'Sommeil', nap: 'Sieste', long_awake: 'Long réveil', half_sleep: 'Demi-sommeil', daytime_sleepiness: 'Somnolence', sleepQuality: 'Qualité du sommeil', wakeQuality: 'Qualité du réveil', dayForm: 'Forme de la journée', notes: 'Traitement et remarques', add: 'Ajouter', save: 'Enregistrer', remove: 'Supprimer', observations: 'Observations', summary: 'Synthèse factuelle', empty: 'Aucune nuit enregistrée.', preview: 'Prévisualiser', export: 'Exporter PDF', qualities: ['Très bon', 'Bon', 'Moyen', 'Mauvais', 'Très mauvais'] }, en: { agenda: 'Diary', edit: 'Entry editor', morning: 'Morning', day: 'Day / Evening', night: 'Night from', bed_time: 'Bedtime', final_get_up: 'Final get-up', night_get_up: 'Night get-up', sleep: 'Sleep', nap: 'Nap', long_awake: 'Long awakening', half_sleep: 'Half-sleep', daytime_sleepiness: 'Sleepiness', sleepQuality: 'Sleep quality', wakeQuality: 'Wake quality', dayForm: 'Day form', notes: 'Treatment and notes', add: 'Add', save: 'Save', remove: 'Delete', observations: 'Observations', summary: 'Factual summary', empty: 'No recorded nights.', preview: 'Preview', export: 'Export PDF', qualities: ['Very good', 'Good', 'Average', 'Bad', 'Very bad'] }, } as const const localInput = (iso: string) => iso ? iso.slice(0, 16) : '' const absolute = (value: string) => value ? new Date(value).toISOString() : '' const offset = (iso: string, startDate: string) => { const start = new Date(`${startDate}T18:00:00`) return Math.max(0, Math.min(100, ((new Date(iso).getTime() - start.getTime()) / 86_400_000) * 100)) } const blank = (): SleepEntryInput => { const now = new Date(); const start = new Date(now); if (now.getHours() < 18) start.setDate(start.getDate() - 1) const end = new Date(start); end.setDate(end.getDate() + 1) const date = (value: Date) => value.toLocaleDateString('en-CA') return { entry_id: '', expected_revision: null, night_start_date: date(start), night_end_date: date(end), created_at: now.toISOString(), updated_at: now.toISOString(), sleep_quality: null, wake_quality: null, day_form: null, treatment_and_notes: '', events: [] } } function QualityPicker({ label, value, onChange, language }: { label: string; value: SleepQuality | null; onChange: (value: SleepQuality | null) => void; language: Language }) { return
{label}{q.map((item, index) => )}
} export function SleepDiaryWorkspace({ period, language }: { period: AnalysisPeriod; language: Language }) { const t = copy[language]; const [snapshot, setSnapshot] = useState> | null>(null); const entries = snapshot?.entries ?? []; const [draft, setDraft] = useState(blank) const [eventType, setEventType] = useState('sleep'); const [pending, setPending] = useState(false); const [error, setError] = useState('') const range = useMemo(() => { if (period === 'all') return {}; const days = Number(period.slice(0, -1)); const end = new Date(); const start = new Date(); start.setDate(end.getDate() - days + 1) return { startDate: start.toLocaleDateString('en-CA'), endDate: end.toLocaleDateString('en-CA') } }, [period]) const reload = () => fetchSleepDiary(range.startDate, range.endDate).then(setSnapshot).catch(() => setError('sleep_diary_unavailable')) useEffect(() => { void reload() }, [period]) const edit = (entry: SleepEntry) => setDraft({ ...entry, expected_revision: entry.revision_id, events: entry.events.map((event) => ({ ...event })) }) const change = (key: K, value: SleepEntryInput[K]) => setDraft((current) => ({ ...current, [key]: value })) const addEvent = () => setDraft((current) => ({ ...current, events: [...current.events, { event_id: '', type: eventType, start_at: new Date().toISOString(), end_at: pointTypes.includes(eventType) ? null : new Date(Date.now() + 3_600_000).toISOString() }] })) const changeEvent = (index: number, patch: Partial) => setDraft((current) => ({ ...current, events: current.events.map((event, currentIndex) => currentIndex === index ? { ...event, ...patch } : event) })) const save = async () => { setPending(true); setError(''); try { await saveSleepEntry({ ...draft, updated_at: new Date().toISOString() }); setDraft(blank()); await reload() } catch (reason) { setError(reason instanceof Error ? reason.message : 'sleep_diary_mutation_failed') } finally { setPending(false) } } return

{t.agenda}

{entries.length === 0 ?

{t.empty}

:
{entries.map((entry) => )}
}

{t.edit}

{t.morning}

change('sleep_quality', value)} language={language}/> change('wake_quality', value)} language={language}/>

{t.day}

change('day_form', value)} language={language}/>
    {draft.events.map((event, index) =>
  • {t[event.type]}{event.end_at !== null && }
  • )}