import { useEffect, useRef, useState } from 'react' import { archiveProgram, createPreparationFromProgram, fetchAllPrograms, fetchProgram, importProgram, validateProgramImport, type ProgramDetail, type ProgramImportPreview, type ProgramListItem, } from '../api/programs' import { useDatePreferences } from '../presentation/DatePreferences' import { formatCivilDate, formatDateTime } from '../presentation/dateFormat' function ProgramDetailView({ programId, onBack, onChanged }: { programId: string onBack: () => void onChanged: () => void }) { const { dateFormat } = useDatePreferences() const [program, setProgram] = useState(null) const [error, setError] = useState('') const [message, setMessage] = useState('') useEffect(() => { const controller = new AbortController() fetchProgram(programId, controller.signal).then(setProgram).catch((reason: unknown) => { if (!controller.signal.aborted) { setError(reason instanceof Error ? reason.message : 'Programme indisponible') } }) return () => controller.abort() }, [programId]) if (error) { return

{error}

} if (!program) { return

Chargement du programme…

} const archive = async () => { setError('') try { await archiveProgram(program.program_id, program.revision_id) setProgram(await fetchProgram(program.program_id)) setMessage('Programme archivé. Ses définitions et préparations existantes sont conservées.') onChanged() } catch (reason) { setError(reason instanceof Error ? reason.message : 'Archivage impossible') } } const prepare = async (sessionId: string) => { setError('') try { const result = await createPreparationFromProgram(program.program_id, sessionId) setMessage('Préparation créée sans livraison Android automatique.') window.history.pushState(null, '', `/seances/preparation/${encodeURIComponent(result.preparation_id)}`) window.dispatchEvent(new PopStateEvent('popstate')) } catch (reason) { setError(reason instanceof Error ? reason.message : 'Création impossible') } } return
{program.state === 'active' && }
{message &&

{message}

} {error &&

{error}

}

PROGRAMME · {program.state === 'active' ? 'ACTIF' : 'ARCHIVÉ'}

{program.title}

{program.note &&

{program.note}

}

{program.start_date ? formatCivilDate(program.start_date, dateFormat) : 'Début non défini'} {' — '} {program.end_date ? formatCivilDate(program.end_date, dateFormat) : 'fin non définie'}

Import {program.source_format} v{program.source_version} · modifié le{' '} {formatDateTime(program.updated_at, dateFormat)}

    {program.sessions.map((session) =>
  1. {session.position + 1}. {session.title}

    {session.session_type === 'max_test' ? 'Test MAX planifié' : 'Entraînement planifié'} {' · '}{session.occurrences.length} exercice {session.occurrences.length > 1 ? 's' : ''}

  2. )}
} export function ProgramsTab({ detailProgramId, onOpen, onBack }: { detailProgramId: string | null onOpen: (programId: string) => void onBack: () => void }) { const { dateFormat } = useDatePreferences() const fileInput = useRef(null) const [items, setItems] = useState([]) const [search, setSearch] = useState('') const [state, setState] = useState('') const [pending, setPending] = useState(true) const [error, setError] = useState('') const [rawImport, setRawImport] = useState('') const [preview, setPreview] = useState(null) const [fileName, setFileName] = useState('') const reload = () => { setPending(true) setError('') const controller = new AbortController() fetchAllPrograms(search, state, controller.signal).then(setItems).catch((reason: unknown) => { if (!controller.signal.aborted) { setError(reason instanceof Error ? reason.message : 'Liste indisponible') } }).finally(() => { if (!controller.signal.aborted) { setPending(false) } }) return controller } useEffect(() => { const controller = reload() return () => controller.abort() }, [search, state]) if (detailProgramId) { return } const choose = async (file: File | undefined) => { setPreview(null) setRawImport('') setError('') setFileName(file?.name ?? '') if (!file) return if (file.size > 512 * 1024) { setError('Le fichier dépasse la limite de 512 Kio.') return } try { const body = await file.text() const result = await validateProgramImport(body) setRawImport(body) setPreview(result) } catch (reason) { setError(reason instanceof Error ? reason.message : 'Fichier invalide') } } const confirmImport = async () => { try { const result = await importProgram(rawImport) setPreview(null) setRawImport('') setFileName('') onOpen(result.program_id) } catch (reason) { setError(reason instanceof Error ? reason.message : 'Import impossible') } } const cancelImport = () => { setPreview(null) setRawImport('') } return
void choose(event.target.files?.[0])} />
{preview &&

Aperçu avant import

{fileName}

Identité : {preview.program_id} · {preview.session_count} séance {preview.session_count > 1 ? 's' : ''}

Aucune donnée n’a encore été enregistrée.

} {error &&

{error}

} {pending &&

Chargement…

} {!pending && !error && items.length === 0 &&

Aucun programme.

}
{items.map((program) => )}
}