// screens.jsx — écrans du parcours (hors Run et Confirm)
//
// CÂBLAGE (2026-08-07, passe « enlever le static ») : plus AUCUNE donnée de
// démonstration. Chaque écran lit le tenant et n'affiche que ce qu'il sait ;
// vide quand il ne sait rien. Règle appliquée partout : un champ visible est
// un champ PERSISTÉ — un champ qu'on saisit et qui se perd en silence est un
// mensonge d'interface. (Revenus nets et bénéfice ont été retirés : la base
// n'a pas encore de colonne pour eux.)
// Langue : chrome via I18N.t(), noms du métier via le lexique du domaine.
const { useState: useStateS, useEffect: useEffectS } = React;
/* ============ CONNEXION ============ */
function LoginScreen({ onLogin }) {
const { t } = useLang();
const [email, setEmail] = useStateS(G4M.email || "");
const [password, setPassword] = useStateS("");
const [busy, setBusy] = useStateS(false);
const [err, setErr] = useStateS(null);
const enter = async () => {
setBusy(true); setErr(null);
try {
// AUTH BÊTA : le mot de passe échange contre un jeton — c'est lui qui
// prouve l'identité à chaque appel. Le serveur résout le tenant depuis
// le compte : le navigateur ne le choisit jamais.
const me = await G4M.login(email.trim(), password);
if (!me || !me.tenantId) throw new Error(t("Compte inconnu ou backend injoignable :") + " tenant absent");
onLogin();
} catch (e) {
setErr(e.message);
} finally { setBusy(false); }
};
return (
{t("Connexion sécurisée")}
{t("Bienvenue")}
{t("Connectez-vous pour reprendre vos {demandes} ou en commencer une nouvelle.")}
{t("Reprenez là où vous étiez, ou lancez une nouvelle {demande}.")}
{t("Nouvelle {demande}")}
{(session && session.role === "admin") && }
e.key === "Enter" && onNew()}>
{t("Commencer une nouvelle {demande}")}
{t("Téléversez un {formulaire}. On s'occupe du reste.")}
{demandes.length === 0 && !meta.loading && (
{t("Aucune {demande} pour l'instant")}
{t("Votre première {demande} commence par le {formulaire} à remplir. Téléversez-le : on lit les champs, on remplit ce qu'on peut, et on ne vous demande que le reste.")}
{t("Ces informations serviront à remplir ")}{t("tous vos {formulaires}")}{t(" : ce que vous saisissez ici, on ne vous le redemandera plus. ")}
{t("Vous ne savez pas où trouver une information ?")} {t("Cliquez le « ? » à côté du champ.")}
{t("Ce profil ne se remplit ")}{t("qu'une fois")}{t(". Il sert à toutes vos {demandes} : plus vous le complétez, mieux on cible les bons {programmes} et plus le remplissage est précis.")}
{/* --- Identité --- */}
1 {t("Identité de l'entreprise")}
set("raison", v)} ariaLabel={t("Raison sociale")} />
set("neq", v)} ariaLabel="NEQ" />
{t("Tel qu'enregistré à votre dossier. Corrigez-le au besoin.")} set("forme", v)} ariaLabel={t("Statut juridique")} />
set("bn", v)} ariaLabel={t("Numéro d'entreprise fédéral (NE / ARC)")} />
{/* Une vraie date, pas une année : le moteur de dates refuse de
deviner un mois. AAAA-MM-JJ est le format pivot. */}
set("constitution", v)} ariaLabel={t("Date de constitution (AAAA-MM-JJ)")} />
set("employes", v)} ariaLabel={t("Nombre d'employés")} />
set("revenus_bruts", v)} ariaLabel={t("Revenus bruts (chiffre d'affaires)")} options={REVENUE_RANGES} />
set("secteur", v)} ariaLabel={t("Secteur d'activité")} />
{/* --- Adresse officielle --- */}
2 {t("Adresses")}
{t("Adresse officielle de l'entreprise")}
{t("Seule l'adresse officielle est enregistrée à votre dossier pour l'instant.")}
{/* --- Codes d'activité (multiples, persistés en jsonb) --- */}
3 {t("Codes d'activité")}
{t("Une entreprise peut en avoir plusieurs. Tapez votre activité dans le libellé : on cherche dans le référentiel officiel et le code se remplit seul. Indiquez le principal.")}
}
);
}
/* ============ ÉTAPE 3 — TÉLÉVERSEMENT ============ */
function UploadScreen({ onNext, onBack, projectId, onRunStarted }) {
const { t } = useLang();
const [state, setState] = useStateS("idle"); // idle | uploading | done
const [file, setFile] = useStateS(null);
const [err, setErr] = useStateS(null);
const [lang, setLang] = useStateS("");
const inputRef = React.useRef(null);
const pick = () => inputRef.current && inputRef.current.click();
const onFile = async (e) => {
const f = e.target.files && e.target.files[0];
if (!f) return;
if (!projectId) { setErr(t("Aucun dossier ouvert. Connectez-vous d'abord.")); return; }
setFile(f); setErr(null); setState("uploading");
try {
const b64 = await G4M.fileToBase64(f);
const run = await G4M.startRun({
projectId, sourcePdfBase64: b64, lang: lang || null, formDomain: null,
});
setState("done");
if (onRunStarted) onRunStarted(run.id);
} catch (e2) {
// Le moteur refuse tout de suite un PDF sans cases à remplir — le dire
// ici évite d'attendre pour rien.
setErr(e2.message);
setState("idle");
}
};
return (
{t("Formulaires pris en charge : les PDF avec des cases à remplir intégrées.")}
{t(" Pour vérifier en 5 secondes : ouvrez le PDF et cliquez dans une case : si vous pouvez taper, c'est bon. Un scan ou un document à imprimer sera refusé immédiatement, sans frais d'analyse.")}
{state !== "done" ? (
{state === "idle" ? (
<>
{t("Choisissez votre {formulaire}")}
{t("PDF · jusqu'à 25 Mo · vos fichiers restent privés")}
{live
? <>{t("Remplissage automatique : ")}{run.pctAutoFilled ?? "—"} %{t(" · validé par vous : ")}{run.pctHumanValidated ?? "—"} %{t(". Il vous reste à joindre quelques pièces.")}>
: t("Le PDF n'est pas encore prêt.")}
{t("Votre {formulaire} rempli")}
{t("{formulaire} rempli")}
{t("PDF rempli, prêt à signer et à transmettre. Vous pourrez le rouvrir et le modifier à tout moment.")}
{t("Télécharger le PDF rempli")}
{dlErr &&
{dlErr}
}
→ {t("Comment soumettre")}
{t("Rassemblez les {pieces}")}{t(" listées à droite.")}
{t("Faites signer le {formulaire}")}{t(" par la personne autorisée de votre entreprise.")}
{t("Déposez le tout")}{t(" auprès de l'{organisme} avant l'échéance.")}
! {t("{pieces} à joindre")}
{docs.map((d, i) => (
{d.nom}
{d.note}
))}
1 ? "pieces" : "piece") })} />
{t("Retour")}{t("Revenir au tableau de bord")}
);
}
/* ============ helpers de mise en page ============ */
function ScreenShell({ kick, icon, title, lede, hideLede, children }) {
return (