diff --git a/src/modules/ar1/pages/Ar1Wizard.js b/src/modules/ar1/pages/Ar1Wizard.js
index 2a8b83c..5c09ccb 100644
--- a/src/modules/ar1/pages/Ar1Wizard.js
+++ b/src/modules/ar1/pages/Ar1Wizard.js
@@ -41,12 +41,90 @@ const Ar1Wizard = () => {
[form]
);
+ const [stepErrors, setStepErrors] = useState([]);
+
+ // --- Validazione client obbligatori (specchio di app/validation.py BE) ---
+ const isEmptyVal = (v) => {
+ if (v === null || v === undefined) return true;
+ if (typeof v === 'string') return v.trim() === '';
+ if (Array.isArray(v)) return v.length === 0;
+ if (typeof v === 'object') {
+ if ('value' in v) return isEmptyVal(v.value);
+ return Object.values(v).every(isEmptyVal);
+ }
+ return false;
+ };
+
+ const validateQuadro = (quadro, data) => {
+ const errs = [];
+ const d = data || {};
+ if (!quadro || quadro.is_legal_frame) return errs;
+ if (quadro.row_type) {
+ const rows = d.rows || [];
+ if (rows.length === 0) {
+ errs.push(`Quadro ${quadro.id}: obbligatoria almeno una riga di dati`);
+ return errs;
+ }
+ rows.forEach((row, i) => {
+ (quadro.row_fields || []).forEach(f => {
+ if (f.required && !f.show_if && isEmptyVal((row || {})[f.id])) {
+ errs.push(`Quadro ${quadro.id} (riga ${i + 1}): ${f.label}`);
+ }
+ });
+ });
+ return errs;
+ }
+ (quadro.upload_slots || []).forEach(sl => {
+ if (sl.required && isEmptyVal(d[sl.id])) errs.push(`Quadro ${quadro.id}: allegato obbligatorio — ${sl.label}`);
+ });
+ (quadro.fields || []).forEach(f => {
+ if (f.required && !f.show_if && isEmptyVal(d[f.id])) errs.push(`Quadro ${quadro.id}: ${f.label}`);
+ });
+ return errs;
+ };
+
+ // detail BE puo essere stringa o {message, errors[]} (validazione 422)
+ const errDetail = (err) => {
+ const d = err?.detail;
+ if (d && typeof d === 'object') {
+ const list = (d.errors || []).slice(0, 5).join('; ');
+ return `${d.message || 'Errore'}${list ? ': ' + list : ''}`;
+ }
+ return d || 'Errore';
+ };
+
+ // JWT scaduto durante la compilazione: bozza in localStorage + avviso persistente
+ const backupDraft = () => {
+ try {
+ localStorage.setItem(`ar1_draft_${formId}`, JSON.stringify({ ts: Date.now(), quadri: quadriValues }));
+ } catch (e) { /* quota o private mode: pazienza */ }
+ };
+ 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')
+ });
+ };
+
useEffect(() => {
if (!formId) return;
Ar1Service.getForm(formId,
(resp) => {
setForm(resp);
- setQuadriValues(resp.quadri || {});
+ let initial = resp.quadri || {};
+ try {
+ const raw = localStorage.getItem(`ar1_draft_${formId}`);
+ if (raw && resp.status === 'DRAFT') {
+ 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 });
+ }
+ }
+ } catch (e) { /* draft corrotto: ignora */ }
+ setQuadriValues(initial);
setLoading(false);
},
(err) => {
@@ -63,10 +141,15 @@ const Ar1Wizard = () => {
const patch = { [quadroId]: quadriValues[quadroId] || {} };
setSaving(true);
Ar1Service.updateQuadri(formId, patch,
- (resp) => { setSaving(false); setForm(resp); },
+ (resp) => {
+ setSaving(false); setForm(resp);
+ try { localStorage.removeItem(`ar1_draft_${formId}`); } catch (e) { /* noop */ }
+ },
(err) => {
setSaving(false);
- if (toast.current) toast.current.show({ severity: 'warn', summary: 'Save fallito', detail: err?.detail || 'Riprovare' });
+ if (err?.status === 401) { handleAuthExpired(); return; }
+ backupDraft();
+ if (toast.current) toast.current.show({ severity: 'warn', summary: 'Save fallito', detail: errDetail(err) });
}
);
};
@@ -105,6 +188,21 @@ const Ar1Wizard = () => {
const submitFinale = () => {
if (!activeQuadro) return;
+ // Gate client: tutti i quadri obbligatori completi prima della firma
+ const allErrs = [];
+ let firstBadIndex = -1;
+ quadri.forEach((q, i) => {
+ const errs = validateQuadro(q, quadriValues[q.id]);
+ if (errs.length && firstBadIndex === -1) firstBadIndex = i;
+ allErrs.push(...errs);
+ });
+ if (allErrs.length) {
+ setStepErrors(allErrs);
+ if (firstBadIndex >= 0) setActiveIndex(firstBadIndex);
+ if (toast.current) toast.current.show({ severity: 'warn', summary: __('Quadri obbligatori incompleti', 'gepafin'), detail: allErrs.slice(0, 4).join(' — '), life: 8000 });
+ return;
+ }
+ setStepErrors([]);
setSubmitting(true);
const patch = { [activeQuadro.id]: quadriValues[activeQuadro.id] || {} };
Ar1Service.updateQuadri(formId, patch,
@@ -112,18 +210,23 @@ const Ar1Wizard = () => {
Ar1Service.submitForSignature(formId,
() => {
setSubmitting(false);
+ try { localStorage.removeItem(`ar1_draft_${formId}`); } catch (e) { /* noop */ }
if (toast.current) toast.current.show({ severity: 'success', summary: 'OK', detail: 'Modulo pronto per la firma' });
setTimeout(() => navigate(`/ar1/signature/${formId}`), 600);
},
(err) => {
setSubmitting(false);
- if (toast.current) toast.current.show({ severity: 'error', summary: 'Errore', detail: err?.detail || 'Submit fallito' });
+ if (err?.status === 401) { handleAuthExpired(); return; }
+ const beErrs = (err?.detail && typeof err.detail === 'object' && err.detail.errors) || [];
+ if (beErrs.length) setStepErrors(beErrs);
+ if (toast.current) toast.current.show({ severity: 'error', summary: 'Errore', detail: errDetail(err) });
}
);
},
(err) => {
setSubmitting(false);
- if (toast.current) toast.current.show({ severity: 'error', summary: 'Errore', detail: err?.detail || 'Save fallito' });
+ if (err?.status === 401) { handleAuthExpired(); return; }
+ if (toast.current) toast.current.show({ severity: 'error', summary: 'Errore', detail: errDetail(err) });
}
);
};
@@ -351,14 +454,34 @@ const Ar1Wizard = () => {
model={steps}
activeIndex={safeIndex}
onSelect={(e) => {
- if (!isReadonly && activeQuadro) saveQuadro(activeQuadro.id);
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 });
+ return;
+ }
+ setStepErrors([]);
+ }
+ if (!isReadonly && activeQuadro) saveQuadro(activeQuadro.id);
setActiveIndex(next);
}}
readOnly={false}
style={{ marginBottom: 20 }}
/>
+ {stepErrors.length > 0 && (
+
+ {__('Campi obbligatori mancanti', 'gepafin')}:
+
+ {stepErrors.slice(0, 10).map((e, i) => - {e}
)}
+ {stepErrors.length > 10 && - {__('...e altri', 'gepafin')} {stepErrors.length - 10}
}
+
+
+ } />
+ )}