// questions.jsx — Écran « À confirmer » (étape 5/6) + 3 variantes tweakables.
// Le moteur a proposé une valeur pour chaque champ ; l'utilisateur valide ou ajuste.
//
// CÂBLAGE (2026-08-07, passe « enlever le static ») : les champs viennent du
// RUN réel, point — le jeu de champs de démonstration est parti avec data.jsx.
// Deux portes y mènent : `missingFields[]` (questions) ou `reviewFields[]`
// (révision : tout est rempli, personne n'a confirmé). La confirmation
// distingue ce qui a été EXPLICITEMENT validé — c'est cette liste qui porte
// l'imputabilité et qui seule nourrit la mémoire d'entreprise.
const { useState: useStateQ } = React;
/** missingFields (GraphQL) → la forme attendue par les 3 variantes. */
function fieldsFromRun(run) {
const WHY = {
not_in_context: I18N.t("Cette information ne figure ni dans votre profil ni dans votre dossier."),
low_confidence: I18N.t("L'assistant a une proposition, mais préfère votre confirmation."),
requires_user_judgment: I18N.t("Cette réponse demande votre jugement et vous engage."),
};
return (run && run.missingFields ? run.missingFields : []).map((m) => ({
id: m.fieldName,
label: m.question || m.fieldName,
explication: WHY[m.whyMissing] || I18N.t("À vérifier avant l'envoi de votre {demande}."),
proposition: m.suggestedValue || "",
valeur: m.suggestedValue || "",
mono: ["number", "date"].includes(m.fieldType),
choices: m.choices || null,
}));
}
/** reviewFields (phase awaiting_review) → la même forme, avec l'origine.
* Le moteur a tout rempli : ce n'est plus « répondre », c'est RELIRE. */
function fieldsFromReview(run) {
const ORIGIN = {
context: I18N.t("Repris de votre profil d'entreprise."),
memory: I18N.t("Réponse que vous aviez déjà validée sur un autre {formulaire}."),
validated: I18N.t("Déjà validé par vous."),
inferred: I18N.t("Rédigé par l'IA à partir de votre dossier. À relire."),
};
return (run && run.reviewFields ? run.reviewFields : []).map((f) => ({
id: f.fieldName,
label: f.label || f.fieldName,
explication: ORIGIN[f.origin] || ORIGIN.inferred,
proposition: f.value == null ? "" : String(f.value),
valeur: f.value == null ? "" : String(f.value),
mono: ["number", "date"].includes(f.fieldType),
origin: f.origin,
}));
}
function ConfirmScreen({ variant, onNext, onBack, runId }) {
const { t } = useLang();
const [run] = useRunPolling(runId);
// Deux portes mènent ici : des questions (awaiting_user) ou une révision
// finale (awaiting_review — le moteur a tout rempli, personne n'a confirmé).
const review = !!(runId && run && run.status === "awaiting_review");
const live = !!(runId && run);
const fields = review ? fieldsFromReview(run) : fieldsFromRun(run);
const [done, setDone] = useStateQ({}); // id -> true
const [vals, setVals] = useStateQ({});
const [sending, setSending] = useStateQ(false);
const [err, setErr] = useStateQ(null);
// Les valeurs se (re)posent quand la source change — un run qui arrive après
// le premier rendu ne doit pas laisser l'écran sur les mocks.
React.useEffect(() => {
const o = {}; fields.forEach((f) => (o[f.id] = f.proposition || f.valeur));
setVals(o);
}, [live, fields.length]);
// Aucun champ en suspens (run absent, ou moteur passé directement à done) :
// on le dit, on ne fabrique pas des questions pour occuper l'écran.
if (!fields.length) {
return (
{live ? t("Le moteur n'a laissé aucune question en suspens pour ce {formulaire}.")
: t("Téléversez d'abord un {formulaire} à l'étape précédente : c'est lui qui lance le travail.")}
{review
? <>{t("Le moteur a rempli ")}{t("{n} champs", { n: fields.length })}{t(" sans avoir besoin de vous poser de question. Relisez-les : ce {formulaire} sera déposé en votre nom, et c'est ")}{t("votre confirmation qui l'engage")}{t(". Corrigez ce que vous voulez.")}>
: <>{t("L'assistant a ")}{t("déjà proposé une réponse")}{t(" pour chacun de ces {n} champs, à partir de votre fiche projet et de vos documents. Vérifiez simplement que c'est juste et corrigez au besoin.", { n: fields.length })}>}
{f.explication}
{f.explication}