init import projet
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
.photoStepGrid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
@@ -0,0 +1,594 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { clearSession, getApiUrl, loadSession } from "@/lib/auth";
|
||||
import { getMyProjects } from "@/lib/projects-client";
|
||||
import {
|
||||
deleteParentPhoto,
|
||||
deleteTrimesterPhoto,
|
||||
getProjectIndices,
|
||||
uploadParentPhoto,
|
||||
uploadTrimesterPhoto,
|
||||
upsertBabyIndices,
|
||||
upsertBabyTrimester,
|
||||
upsertParentIndices,
|
||||
} from "@/lib/indices-client";
|
||||
import type {
|
||||
BabyIndices,
|
||||
ParentIndices,
|
||||
ParentType,
|
||||
ProjectIndicesResponse,
|
||||
Trimester,
|
||||
} from "@/types/indices";
|
||||
import type { ProjectSummary } from "@/types/projects";
|
||||
import { PhotoField } from "@/features/indices/components/PhotoField";
|
||||
import styles from "./page.module.css";
|
||||
import wizardStyles from "../../new/page.module.css";
|
||||
|
||||
const TRIMESTERS: Trimester[] = ["DATATION", "T1", "T2", "T3"];
|
||||
const TRIMESTER_LABELS: Record<Trimester, string> = {
|
||||
DATATION: "Datation (1ère échographie)",
|
||||
T1: "1er trimestre",
|
||||
T2: "2ème trimestre",
|
||||
T3: "3ème trimestre",
|
||||
};
|
||||
|
||||
type ParentDraft = {
|
||||
poids: string;
|
||||
taille: string;
|
||||
perimCranien: string;
|
||||
dateNaissance: string;
|
||||
};
|
||||
|
||||
type TrimesterDraft = {
|
||||
date: string;
|
||||
note: string;
|
||||
poids: string;
|
||||
taille: string;
|
||||
perimCranien: string;
|
||||
};
|
||||
|
||||
function emptyParentDraft(): ParentDraft {
|
||||
return { poids: "", taille: "", perimCranien: "", dateNaissance: "" };
|
||||
}
|
||||
|
||||
function emptyTrimesterDraft(): TrimesterDraft {
|
||||
return { date: "", note: "", poids: "", taille: "", perimCranien: "" };
|
||||
}
|
||||
|
||||
function parentToForm(p: ParentIndices | undefined): ParentDraft {
|
||||
if (!p) return emptyParentDraft();
|
||||
return {
|
||||
poids: p.poids != null ? String(p.poids) : "",
|
||||
taille: p.taille != null ? String(p.taille) : "",
|
||||
perimCranien: p.perimCranien != null ? String(p.perimCranien) : "",
|
||||
dateNaissance: p.dateNaissance ? p.dateNaissance.slice(0, 10) : "",
|
||||
};
|
||||
}
|
||||
|
||||
function babyTrimesterToForm(baby: BabyIndices | undefined, tri: Trimester): TrimesterDraft {
|
||||
const entry = baby?.trimesters.find((t) => t.trimester === tri);
|
||||
if (!entry) return emptyTrimesterDraft();
|
||||
return {
|
||||
date: entry.date ? entry.date.slice(0, 10) : "",
|
||||
note: entry.note ?? "",
|
||||
poids: entry.poids != null ? String(entry.poids) : "",
|
||||
taille: entry.taille != null ? String(entry.taille) : "",
|
||||
perimCranien: entry.perimCranien != null ? String(entry.perimCranien) : "",
|
||||
};
|
||||
}
|
||||
|
||||
function parseOptionalFloat(val: string): number | undefined {
|
||||
const n = parseFloat(val);
|
||||
return Number.isNaN(n) ? undefined : n;
|
||||
}
|
||||
|
||||
/* ─────────────────────────── Page ─────────────────────────── */
|
||||
|
||||
export default function AdminIndicesPage() {
|
||||
const params = useParams<{ projectId: string }>();
|
||||
const projectId = params.projectId;
|
||||
const router = useRouter();
|
||||
|
||||
const [ready, setReady] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [message, setMessage] = useState("");
|
||||
const [stepIndex, setStepIndex] = useState(0);
|
||||
|
||||
const [project, setProject] = useState<ProjectSummary | null>(null);
|
||||
const [indices, setIndices] = useState<ProjectIndicesResponse | null>(null);
|
||||
|
||||
// Papa
|
||||
const [papaDraft, setPapaDraft] = useState<ParentDraft>(emptyParentDraft());
|
||||
const [papaPhotos, setPapaPhotos] = useState<Array<{ id: string; url: string }>>([]);
|
||||
|
||||
// Maman
|
||||
const [mamanDraft, setManamDraft] = useState<ParentDraft>(emptyParentDraft());
|
||||
const [mamanPhotos, setManamPhotos] = useState<Array<{ id: string; url: string }>>([]);
|
||||
|
||||
// Bébés — datation + trimestres + DPA
|
||||
const [babyDpas, setBabyDpas] = useState<Record<number, string>>({});
|
||||
const [trimesterDrafts, setTrimesterDrafts] = useState<Record<string, TrimesterDraft>>({});
|
||||
const [trimesterPhotos, setTrimesterPhotos] = useState<Record<string, Array<{ id: string; url: string }>>>({});
|
||||
|
||||
const apiBaseUrl = getApiUrl();
|
||||
|
||||
const babyCount = project?.babyCount ?? 1;
|
||||
|
||||
// Steps: 0=papa mesures, 1=papa photos, 2=maman mesures, 3=maman photos,
|
||||
// puis par bébé: 4+4n=datation(+DPA), 4+4n+1=T1, 4+4n+2=T2, 4+4n+3=T3
|
||||
// Dernier step = recap
|
||||
const totalBabySteps = babyCount * 4;
|
||||
const totalSteps = 4 + totalBabySteps + 1; // +1 recap
|
||||
const recapStepIndex = totalSteps - 1;
|
||||
|
||||
const getBabyLabel = useCallback(
|
||||
(idx: number) => project?.babies?.find((b) => b.babyIndex === idx)?.label ?? `Bébé ${idx}`,
|
||||
[project],
|
||||
);
|
||||
|
||||
const getBabyStepInfo = (step: number): { babyIndex: number; trimester: Trimester } | null => {
|
||||
if (step < 4 || step >= 4 + totalBabySteps) return null;
|
||||
const offset = step - 4;
|
||||
const babyIndex = Math.floor(offset / 4) + 1;
|
||||
const triIndex = offset % 4;
|
||||
return { babyIndex, trimester: TRIMESTERS[triIndex] };
|
||||
};
|
||||
|
||||
const triKey = (babyIndex: number, tri: Trimester) => `${babyIndex}-${tri}`;
|
||||
|
||||
/* ─── Init ─── */
|
||||
useEffect(() => {
|
||||
const session = loadSession();
|
||||
if (!session) { router.replace("/"); return; }
|
||||
if (session.user.role !== "ADMIN") { router.replace("/predictions"); return; }
|
||||
|
||||
Promise.all([getMyProjects(), getProjectIndices(projectId)])
|
||||
.then(([projects, idx]) => {
|
||||
const proj = projects.find((p) => p.id === projectId) ?? null;
|
||||
setProject(proj);
|
||||
setIndices(idx);
|
||||
|
||||
// Init papa
|
||||
const papa = idx.parentIndices.find((p) => p.parentType === "PAPA");
|
||||
setPapaDraft(parentToForm(papa));
|
||||
setPapaPhotos(papa?.photos.map((ph) => ({ id: ph.id, url: ph.url })) ?? []);
|
||||
|
||||
// Init maman
|
||||
const maman = idx.parentIndices.find((p) => p.parentType === "MAMAN");
|
||||
setManamDraft(parentToForm(maman));
|
||||
setManamPhotos(maman?.photos.map((ph) => ({ id: ph.id, url: ph.url })) ?? []);
|
||||
|
||||
// Init bébés
|
||||
const dpas: Record<number, string> = {};
|
||||
const triDrafts: Record<string, TrimesterDraft> = {};
|
||||
const triPh: Record<string, Array<{ id: string; url: string }>> = {};
|
||||
const bc = proj?.babyCount ?? 1;
|
||||
for (let i = 1; i <= bc; i++) {
|
||||
const baby = idx.babyIndices.find((b) => b.babyIndex === i);
|
||||
dpas[i] = baby?.dpa ? baby.dpa.slice(0, 10) : "";
|
||||
for (const tri of TRIMESTERS) {
|
||||
triDrafts[triKey(i, tri)] = babyTrimesterToForm(baby, tri);
|
||||
const existingTri = baby?.trimesters.find((t) => t.trimester === tri);
|
||||
triPh[triKey(i, tri)] = existingTri?.photos.map((ph) => ({ id: ph.id, url: ph.url })) ?? [];
|
||||
}
|
||||
}
|
||||
setBabyDpas(dpas);
|
||||
setTrimesterDrafts(triDrafts);
|
||||
setTrimesterPhotos(triPh);
|
||||
})
|
||||
.catch((err) => {
|
||||
setMessage(err instanceof Error ? err.message : "Erreur de chargement");
|
||||
})
|
||||
.finally(() => setReady(true));
|
||||
}, [projectId, router]);
|
||||
|
||||
/* ─── Save current step ─── */
|
||||
const saveCurrentStep = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setMessage("");
|
||||
try {
|
||||
if (stepIndex === 0) {
|
||||
await upsertParentIndices(projectId, "PAPA", {
|
||||
poids: parseOptionalFloat(papaDraft.poids),
|
||||
taille: parseOptionalFloat(papaDraft.taille),
|
||||
perimCranien: parseOptionalFloat(papaDraft.perimCranien),
|
||||
dateNaissance: papaDraft.dateNaissance || null,
|
||||
});
|
||||
} else if (stepIndex === 2) {
|
||||
await upsertParentIndices(projectId, "MAMAN", {
|
||||
poids: parseOptionalFloat(mamanDraft.poids),
|
||||
taille: parseOptionalFloat(mamanDraft.taille),
|
||||
perimCranien: parseOptionalFloat(mamanDraft.perimCranien),
|
||||
dateNaissance: mamanDraft.dateNaissance || null,
|
||||
});
|
||||
} else {
|
||||
const babyStep = getBabyStepInfo(stepIndex);
|
||||
if (babyStep) {
|
||||
const { babyIndex, trimester } = babyStep;
|
||||
const triOffset = stepIndex - 4 - (babyIndex - 1) * 4;
|
||||
if (triOffset === 0) {
|
||||
// Datation step: DPA + données de la première échographie
|
||||
await upsertBabyIndices(projectId, babyIndex, { dpa: babyDpas[babyIndex] || null });
|
||||
}
|
||||
const draft = trimesterDrafts[triKey(babyIndex, trimester)] ?? emptyTrimesterDraft();
|
||||
await upsertBabyTrimester(projectId, babyIndex, trimester, {
|
||||
date: draft.date || null,
|
||||
note: draft.note || null,
|
||||
poids: parseOptionalFloat(draft.poids),
|
||||
taille: parseOptionalFloat(draft.taille),
|
||||
perimCranien: parseOptionalFloat(draft.perimCranien),
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof Error && err.message.includes("Session")) {
|
||||
clearSession();
|
||||
router.replace("/");
|
||||
return false;
|
||||
}
|
||||
setMessage(err instanceof Error ? err.message : "Erreur de sauvegarde");
|
||||
return false;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
return true;
|
||||
}, [stepIndex, projectId, papaDraft, mamanDraft, babyDpas, trimesterDrafts, getBabyStepInfo, router]);
|
||||
|
||||
const goNext = async () => {
|
||||
if (stepIndex === recapStepIndex) return;
|
||||
const photoStep = stepIndex === 1 || stepIndex === 3;
|
||||
let ok = true;
|
||||
if (!photoStep) {
|
||||
ok = (await saveCurrentStep()) !== false;
|
||||
}
|
||||
if (ok) setStepIndex((s) => s + 1);
|
||||
};
|
||||
|
||||
const goBack = () => setStepIndex((s) => Math.max(0, s - 1));
|
||||
|
||||
/* ─── Photo upload ─── */
|
||||
const handleParentPhotosAdd = async (parentType: ParentType, files: File[]) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
for (const file of files) {
|
||||
const photo = await uploadParentPhoto(projectId, parentType, file);
|
||||
if (parentType === "PAPA") {
|
||||
setPapaPhotos((prev) => [...prev, { id: photo.id, url: photo.url }]);
|
||||
} else {
|
||||
setManamPhotos((prev) => [...prev, { id: photo.id, url: photo.url }]);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setMessage(err instanceof Error ? err.message : "Erreur upload");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTrimesterPhotosAdd = async (babyIndex: number, trimester: Trimester, files: File[]) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
for (const file of files) {
|
||||
const photo = await uploadTrimesterPhoto(projectId, babyIndex, trimester, file);
|
||||
const k = triKey(babyIndex, trimester);
|
||||
setTrimesterPhotos((prev) => ({ ...prev, [k]: [...(prev[k] ?? []), { id: photo.id, url: photo.url }] }));
|
||||
}
|
||||
} catch (err) {
|
||||
setMessage(err instanceof Error ? err.message : "Erreur upload");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const removeParentPhoto = async (parentType: ParentType, photoId: string) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await deleteParentPhoto(projectId, parentType, photoId);
|
||||
if (parentType === "PAPA") {
|
||||
setPapaPhotos((prev) => prev.filter((p) => p.id !== photoId));
|
||||
} else {
|
||||
setManamPhotos((prev) => prev.filter((p) => p.id !== photoId));
|
||||
}
|
||||
} catch (err) {
|
||||
setMessage(err instanceof Error ? err.message : "Erreur suppression");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const removeTrimesterPhoto = async (babyIndex: number, trimester: Trimester, photoId: string) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await deleteTrimesterPhoto(projectId, babyIndex, trimester, photoId);
|
||||
const k = triKey(babyIndex, trimester);
|
||||
setTrimesterPhotos((prev) => ({ ...prev, [k]: (prev[k] ?? []).filter((p) => p.id !== photoId) }));
|
||||
} catch (err) {
|
||||
setMessage(err instanceof Error ? err.message : "Erreur suppression");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/* ─── Helpers render ─── */
|
||||
const renderParentForm = (parentType: ParentType, draft: ParentDraft, setDraft: (d: ParentDraft) => void) => {
|
||||
const label = parentType === "PAPA" ? "Papa" : "Maman";
|
||||
return (
|
||||
<div className={wizardStyles.formGrid}>
|
||||
<p className={wizardStyles.helpText}>
|
||||
Renseigne les informations physiques de naissance de {label}. Tous les champs sont optionnels.
|
||||
</p>
|
||||
<div className="field">
|
||||
<label htmlFor={`${parentType}-poids`}>Poids de naissance (kg)</label>
|
||||
<input
|
||||
id={`${parentType}-poids`}
|
||||
type="number"
|
||||
step="0.1"
|
||||
placeholder="ex: 3.4"
|
||||
value={draft.poids}
|
||||
onChange={(e) => setDraft({ ...draft, poids: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor={`${parentType}-taille`}>Taille de naissance (cm)</label>
|
||||
<input
|
||||
id={`${parentType}-taille`}
|
||||
type="number"
|
||||
step="1"
|
||||
placeholder="ex: 51"
|
||||
value={draft.taille}
|
||||
onChange={(e) => setDraft({ ...draft, taille: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor={`${parentType}-perim`}>Périmètre crânien de naissance (cm)</label>
|
||||
<input
|
||||
id={`${parentType}-perim`}
|
||||
type="number"
|
||||
step="0.1"
|
||||
placeholder="ex: 35"
|
||||
value={draft.perimCranien}
|
||||
onChange={(e) => setDraft({ ...draft, perimCranien: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor={`${parentType}-dob`}>Date de naissance</label>
|
||||
<input
|
||||
id={`${parentType}-dob`}
|
||||
type="date"
|
||||
value={draft.dateNaissance}
|
||||
onChange={(e) => setDraft({ ...draft, dateNaissance: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderPhotoStep = (
|
||||
parentType: ParentType,
|
||||
photos: Array<{ id: string; url: string }>,
|
||||
label: string,
|
||||
) => (
|
||||
<div className={styles.photoStepGrid}>
|
||||
<p className={wizardStyles.helpText}>
|
||||
Ajoute jusqu'à 5 photos pour {label} (échographies, portraits...).
|
||||
</p>
|
||||
<PhotoField
|
||||
photos={photos}
|
||||
maxPhotos={5}
|
||||
loading={loading}
|
||||
apiBaseUrl={apiBaseUrl}
|
||||
onAdd={(files) => void handleParentPhotosAdd(parentType, files)}
|
||||
onRemove={(id) => void removeParentPhoto(parentType, id)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderTrimesterStep = (babyIndex: number, trimester: Trimester) => {
|
||||
const k = triKey(babyIndex, trimester);
|
||||
const draft = trimesterDrafts[k] ?? emptyTrimesterDraft();
|
||||
const photos = trimesterPhotos[k] ?? [];
|
||||
const triOffset = (stepIndex - 4) % 4;
|
||||
const isDpaStep = triOffset === 0;
|
||||
|
||||
return (
|
||||
<div className={wizardStyles.formGrid}>
|
||||
{isDpaStep && (
|
||||
<div className="field">
|
||||
<label htmlFor={`dpa-${babyIndex}`}>
|
||||
Date prévue d'accouchement — {getBabyLabel(babyIndex)}
|
||||
</label>
|
||||
<input
|
||||
id={`dpa-${babyIndex}`}
|
||||
type="date"
|
||||
value={babyDpas[babyIndex] ?? ""}
|
||||
onChange={(e) => setBabyDpas((prev) => ({ ...prev, [babyIndex]: e.target.value }))}
|
||||
/>
|
||||
<p className={wizardStyles.helpText}>Optionnel — peut être renseignée plus tard.</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className={wizardStyles.helpText}>
|
||||
{TRIMESTER_LABELS[trimester]} — données d'échographie pour {getBabyLabel(babyIndex)}. Tous les champs sont optionnels.
|
||||
</p>
|
||||
|
||||
<div className="field">
|
||||
<label htmlFor={`${k}-date`}>Date de l'échographie</label>
|
||||
<input
|
||||
id={`${k}-date`}
|
||||
type="date"
|
||||
value={draft.date}
|
||||
onChange={(e) => setTrimesterDrafts((prev) => ({ ...prev, [k]: { ...draft, date: e.target.value } }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor={`${k}-poids`}>Poids estimé (kg)</label>
|
||||
<input
|
||||
id={`${k}-poids`}
|
||||
type="number"
|
||||
step="0.001"
|
||||
placeholder="ex: 0.185"
|
||||
value={draft.poids}
|
||||
onChange={(e) => setTrimesterDrafts((prev) => ({ ...prev, [k]: { ...draft, poids: e.target.value } }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor={`${k}-taille`}>Taille estimée (cm)</label>
|
||||
<input
|
||||
id={`${k}-taille`}
|
||||
type="number"
|
||||
step="0.1"
|
||||
placeholder="ex: 16"
|
||||
value={draft.taille}
|
||||
onChange={(e) => setTrimesterDrafts((prev) => ({ ...prev, [k]: { ...draft, taille: e.target.value } }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor={`${k}-perim`}>Périmètre crânien (cm)</label>
|
||||
<input
|
||||
id={`${k}-perim`}
|
||||
type="number"
|
||||
step="0.1"
|
||||
placeholder="ex: 12"
|
||||
value={draft.perimCranien}
|
||||
onChange={(e) => setTrimesterDrafts((prev) => ({ ...prev, [k]: { ...draft, perimCranien: e.target.value } }))}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor={`${k}-note`}>Note / observations</label>
|
||||
<textarea
|
||||
id={`${k}-note`}
|
||||
rows={3}
|
||||
placeholder="Tout va bien, bébé est en bonne santé..."
|
||||
value={draft.note}
|
||||
onChange={(e) => setTrimesterDrafts((prev) => ({ ...prev, [k]: { ...draft, note: e.target.value } }))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Photos trimestre */}
|
||||
<div>
|
||||
<p className={wizardStyles.helpText}>Photos / échographies (max 5)</p>
|
||||
<PhotoField
|
||||
photos={photos}
|
||||
maxPhotos={5}
|
||||
loading={loading}
|
||||
apiBaseUrl={apiBaseUrl}
|
||||
onAdd={(files) => void handleTrimesterPhotosAdd(babyIndex, trimester, files)}
|
||||
onRemove={(id) => void removeTrimesterPhoto(babyIndex, trimester, id)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getStepTitle = (): string => {
|
||||
if (stepIndex === 0) return "Papa — informations de naissance";
|
||||
if (stepIndex === 1) return "Papa — photos";
|
||||
if (stepIndex === 2) return "Maman — informations de naissance";
|
||||
if (stepIndex === 3) return "Maman — photos";
|
||||
if (stepIndex === recapStepIndex) return "Récapitulatif";
|
||||
const babyStep = getBabyStepInfo(stepIndex);
|
||||
if (babyStep) {
|
||||
return `${getBabyLabel(babyStep.babyIndex)} — ${TRIMESTER_LABELS[babyStep.trimester]}`;
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
if (!ready) {
|
||||
return (
|
||||
<main className={`app-shell ${wizardStyles.page}`}>
|
||||
<section className={`panel ${wizardStyles.panel}`}>
|
||||
<p>Chargement...</p>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main className={`app-shell ${wizardStyles.page}`}>
|
||||
<section className={`panel ${wizardStyles.header}`}>
|
||||
<div>
|
||||
<p className="mono">Admin / Projets / {project?.name ?? projectId} / Indices</p>
|
||||
<h1>Indices de grossesse</h1>
|
||||
<p>Renseigne les informations physiques de naissance des parents et les données du bébé, trimestre par trimestre.</p>
|
||||
</div>
|
||||
<div className={wizardStyles.headerActions}>
|
||||
<Link className="btn btn-soft" href="/admin">
|
||||
Retour admin
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className={`panel ${wizardStyles.panel}`}>
|
||||
<div className={wizardStyles.stepHeader}>
|
||||
<span className={wizardStyles.stepPill}>
|
||||
Étape {stepIndex + 1} / {totalSteps}
|
||||
</span>
|
||||
<strong style={{ fontSize: "0.95rem" }}>{getStepTitle()}</strong>
|
||||
</div>
|
||||
|
||||
{/* ── Étapes ── */}
|
||||
{stepIndex === 0 && renderParentForm("PAPA", papaDraft, setPapaDraft)}
|
||||
{stepIndex === 1 && renderPhotoStep("PAPA", papaPhotos, "papa")}
|
||||
{stepIndex === 2 && renderParentForm("MAMAN", mamanDraft, setManamDraft)}
|
||||
{stepIndex === 3 && renderPhotoStep("MAMAN", mamanPhotos, "maman")}
|
||||
|
||||
{stepIndex >= 4 && stepIndex < 4 + totalBabySteps && (() => {
|
||||
const babyStep = getBabyStepInfo(stepIndex);
|
||||
if (!babyStep) return null;
|
||||
return renderTrimesterStep(babyStep.babyIndex, babyStep.trimester);
|
||||
})()}
|
||||
|
||||
{stepIndex === recapStepIndex && (
|
||||
<div className={wizardStyles.reviewPanel}>
|
||||
<h3>Récapitulatif</h3>
|
||||
<p><strong>Projet :</strong> {project?.name ?? projectId}</p>
|
||||
<p><strong>Papa (naissance) :</strong> {papaDraft.poids ? `${papaDraft.poids} kg` : "—"}, {papaDraft.taille ? `${papaDraft.taille} cm` : "—"} — {papaPhotos.length} photo(s)</p>
|
||||
<p><strong>Maman (naissance) :</strong> {mamanDraft.poids ? `${mamanDraft.poids} kg` : "—"}, {mamanDraft.taille ? `${mamanDraft.taille} cm` : "—"} — {mamanPhotos.length} photo(s)</p>
|
||||
{Array.from({ length: babyCount }, (_, i) => i + 1).map((bi) => (
|
||||
<p key={bi}>
|
||||
<strong>{getBabyLabel(bi)} :</strong>{" "}
|
||||
DPA {babyDpas[bi] || "—"} —{" "}
|
||||
{TRIMESTERS.map((t) => {
|
||||
const ph = trimesterPhotos[triKey(bi, t)]?.length ?? 0;
|
||||
return `${TRIMESTER_LABELS[t]}: ${ph} photo(s)`;
|
||||
}).join(", ")}
|
||||
</p>
|
||||
))}
|
||||
<p>Les informations ont été sauvegardées au fil des étapes.</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{message ? <p className={wizardStyles.message}>{message}</p> : null}
|
||||
|
||||
<div className={wizardStyles.wizardActions}>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-soft"
|
||||
onClick={goBack}
|
||||
disabled={loading || stepIndex === 0}
|
||||
>
|
||||
Retour
|
||||
</button>
|
||||
|
||||
{stepIndex === recapStepIndex ? (
|
||||
<Link className="btn btn-primary" href="/admin">
|
||||
Terminer
|
||||
</Link>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary"
|
||||
disabled={loading}
|
||||
onClick={() => void goNext()}
|
||||
>
|
||||
{loading ? "Sauvegarde..." : "Continuer"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user