From 6dc715d997c30ff237bdbe8d525ebec6e2929ac1 Mon Sep 17 00:00:00 2001 From: Kitzanos Date: Sat, 4 Jul 2026 08:43:54 +0200 Subject: [PATCH] =?UTF-8?q?fix(ar1):=20esiti=20test=20UI=204/7=20=E2=80=94?= =?UTF-8?q?=20barra=20Steps,=20banner=20401,=20recap=20firma,=20note=20su?= =?UTF-8?q?=20NO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Report test UI 4/7/2026 (P1, P2a, P3, P6, P7): - P1: salto in avanti dalla barra Steps valida TUTTI i quadri intermedi (validateRange) e posiziona sul primo incompleto; indietro sempre libero - P2a: selezione No azzera la nota nel payload (niente note orfane persistite) - P3: Firma non rilevata come banner fisso in pagina (pattern recap), non toast - P6: banner sessione scaduta unico (ref dedup) + autosave sospeso dopo il primo 401 (solo backup locale, niente raffiche di PUT) - P7: Bozza recuperata come Message in pagina con dismiss, non toast volatile --- src/modules/ar1/pages/Ar1Signature.js | 16 +++++---- src/modules/ar1/pages/Ar1Wizard.js | 52 +++++++++++++++++++++------ 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/src/modules/ar1/pages/Ar1Signature.js b/src/modules/ar1/pages/Ar1Signature.js index cb7e9af..967f41a 100644 --- a/src/modules/ar1/pages/Ar1Signature.js +++ b/src/modules/ar1/pages/Ar1Signature.js @@ -79,6 +79,7 @@ const Ar1Signature = () => { const [uploadRecap, setUploadRecap] = useState(null); const handleUploadSigned = (event) => { + setUploadRecap(null); const file = event.files?.[0]; if (!file) return; @@ -111,14 +112,17 @@ const Ar1Signature = () => { setUploading(false); resetUploadInput(); if (err?.detail?.code === 'NO_SIGNATURE_DETECTED') { - if (toast.current) toast.current.show({ - severity: 'error', - summary: 'Firma non rilevata', - detail: 'Il file caricato non contiene una firma digitale valida. Firmare il PDF con il proprio strumento FEQ e ricaricarlo.', - life: 6000 + setUploadRecap({ + severity: 'error', outcome: 'NO_SIGNATURE_DETECTED', + title: __('Firma non rilevata', 'gepafin'), + body: __('Il file caricato non contiene una firma digitale valida. Firmare il PDF con il proprio strumento FEQ (CAdES .p7m o PAdES) e ricaricarlo.', 'gepafin') }); } else { - if (toast.current) toast.current.show({ severity: 'error', summary: 'Errore', detail: typeof err?.detail === 'string' ? err.detail : (err?.detail?.message || 'Upload fallito') }); + setUploadRecap({ + severity: 'error', outcome: 'UPLOAD_ERROR', + title: __('Errore', 'gepafin'), + body: typeof err?.detail === 'string' ? err.detail : (err?.detail?.message || __('Upload fallito', 'gepafin')) + }); } } ); diff --git a/src/modules/ar1/pages/Ar1Wizard.js b/src/modules/ar1/pages/Ar1Wizard.js index acc8a89..36b18fd 100644 --- a/src/modules/ar1/pages/Ar1Wizard.js +++ b/src/modules/ar1/pages/Ar1Wizard.js @@ -42,6 +42,9 @@ const Ar1Wizard = () => { ); const [stepErrors, setStepErrors] = useState([]); + const [authExpired, setAuthExpired] = useState(false); + const authExpiredRef = useRef(false); + const [draftNotice, setDraftNotice] = useState(null); // --- Validazione client obbligatori (specchio di app/validation.py BE) --- const isEmptyVal = (v) => { @@ -102,6 +105,17 @@ const Ar1Wizard = () => { return errs; }; + // Valida tutti i quadri in [from, to): al salto in avanti nessun quadro + // intermedio puo essere incompleto (report test 4/7/2026 P1) + const validateRange = (fromIdx, toIdx) => { + for (let i = fromIdx; i < toIdx; i++) { + const q = quadri[i]; + const errs = validateQuadro(q, quadriValues[q.id]); + if (errs.length) return { errs, badIndex: i }; + } + return null; + }; + // detail BE puo essere stringa o {message, errors[]} (validazione 422) const errDetail = (err) => { const d = err?.detail; @@ -120,11 +134,9 @@ const Ar1Wizard = () => { }; const handleAuthExpired = () => { backupDraft(); - if (toast.current) toast.current.show({ - severity: 'warn', sticky: true, - summary: __('Sessione scaduta', 'gepafin'), - detail: __('Il salvataggio non e riuscito perche la sessione e scaduta. I dati compilati sono conservati in questo browser: effettua di nuovo il login e riapri il modulo per recuperarli.', 'gepafin') - }); + if (authExpiredRef.current) return; // banner unico, niente pile di avvisi (P6) + authExpiredRef.current = true; + setAuthExpired(true); }; useEffect(() => { @@ -139,7 +151,7 @@ const Ar1Wizard = () => { const draft = JSON.parse(raw); if (draft && draft.quadri) { initial = { ...initial, ...draft.quadri }; - if (toast.current) toast.current.show({ severity: 'info', summary: 'Bozza recuperata', detail: 'Ripristinati i dati non salvati della sessione precedente.', life: 6000 }); + setDraftNotice(__('Bozza recuperata: ripristinati i dati non salvati della sessione precedente.', 'gepafin')); } } } catch (e) { /* draft corrotto: ignora */ } @@ -157,6 +169,7 @@ const Ar1Wizard = () => { const saveQuadro = (quadroId) => { if (isReadonly) return; + if (authExpiredRef.current) { backupDraft(); return; } // 401 gia noto: solo backup locale const patch = { [quadroId]: quadriValues[quadroId] || {} }; setSaving(true); Ar1Service.updateQuadri(formId, patch, @@ -333,7 +346,7 @@ const Ar1Wizard = () => {
- onChange(field.id, { ...v, value: 'no' })} disabled={disabled} /> + onChange(field.id, { value: 'no' })} disabled={disabled} />
@@ -475,10 +488,11 @@ const Ar1Wizard = () => { onSelect={(e) => { const next = Math.max(0, Math.min(e.index, quadri.length - 1)); if (!isReadonly && next > safeIndex) { - const errs = validateQuadro(activeQuadro, quadriValues[activeQuadro.id]); - if (errs.length) { - setStepErrors(errs); - if (toast.current) toast.current.show({ severity: 'warn', summary: __('Campi obbligatori mancanti', 'gepafin'), detail: errs.slice(0, 3).join(' — '), life: 6000 }); + const bad = validateRange(safeIndex, next); + if (bad) { + setStepErrors(bad.errs); + setActiveIndex(bad.badIndex); + if (toast.current) toast.current.show({ severity: 'warn', summary: __('Campi obbligatori mancanti', 'gepafin'), detail: bad.errs.slice(0, 3).join(' — '), life: 6000 }); return; } setStepErrors([]); @@ -490,6 +504,22 @@ const Ar1Wizard = () => { style={{ marginBottom: 20 }} /> + {authExpired && ( + + {__('Sessione scaduta', 'gepafin')} +
{__('Il salvataggio automatico e sospeso: i dati compilati sono conservati in questo browser. Effettua di nuovo il login e riapri il modulo per recuperarli e continuare.', 'gepafin')}
+ + } /> + )} + {draftNotice && !authExpired && ( + + {draftNotice} +