diff --git a/src/modules/ar1/pages/Ar1AdminConfig.js b/src/modules/ar1/pages/Ar1AdminConfig.js
index 2d3f0fe..981fe86 100644
--- a/src/modules/ar1/pages/Ar1AdminConfig.js
+++ b/src/modules/ar1/pages/Ar1AdminConfig.js
@@ -16,20 +16,58 @@ import { Checkbox } from 'primereact/checkbox';
import { Dialog } from 'primereact/dialog';
import { Tag } from 'primereact/tag';
import { Message } from 'primereact/message';
+import { Dropdown } from 'primereact/dropdown';
+import { Divider } from 'primereact/divider';
import { ConfirmDialog, confirmDialog } from 'primereact/confirmdialog';
// service
import Ar1Service from '../service/ar1Service';
+// ==================================================
+// Mappe label in italiano — SINGLE SOURCE OF TRUTH
+// ==================================================
+
+// Stati template
+const TEMPLATE_STATUS_LABEL = {
+ ACTIVE: 'In uso',
+ ARCHIVED: 'Archiviato',
+ DRAFT: 'Bozza'
+};
+
+// Varianti template
+const VARIANT_LABEL = {
+ A1: 'A1 — Persona Giuridica (societa, ente)',
+ A2: 'A2 — Ditta Individuale (P.IVA persona fisica)',
+ A3: 'A3 — Persona Fisica (senza P.IVA)'
+};
+
+// Kind regole reminder PEC
+const PEC_KIND_LABEL = {
+ AR1_REMINDER_30D: 'Promemoria 30 giorni prima della scadenza',
+ AR1_REMINDER_7D: 'Promemoria 7 giorni prima della scadenza',
+ AR1_EXPIRED: 'Notifica alla scadenza (giorno 0)',
+ AR1_POST_EXPIRED_RECURRING: 'Sollecito ricorrente dopo la scadenza',
+ AR1_BULK_MANUAL: 'Invio manuale massivo (superadmin)'
+};
+
+const PEC_KIND_OPTIONS = [
+ { label: PEC_KIND_LABEL.AR1_REMINDER_30D, value: 'AR1_REMINDER_30D' },
+ { label: PEC_KIND_LABEL.AR1_REMINDER_7D, value: 'AR1_REMINDER_7D' },
+ { label: PEC_KIND_LABEL.AR1_EXPIRED, value: 'AR1_EXPIRED' },
+ { label: PEC_KIND_LABEL.AR1_POST_EXPIRED_RECURRING, value: 'AR1_POST_EXPIRED_RECURRING' },
+ { label: PEC_KIND_LABEL.AR1_BULK_MANUAL, value: 'AR1_BULK_MANUAL' }
+];
+
/**
* Ar1AdminConfig — configurazione AR1 per superadmin.
* URL: /ar1-admin
*
- * 4 sezioni (TabView):
- * 1. Template — lista + layout editor L2 + nuova versione
- * 2. Policy — singleton (validity, popup, auto-archive, category)
- * 3. Regole reminder PEC — CRUD pec-schedule-config
- * 4. Invio massivo PEC — bulk-request-recompilation (dry-run + live)
+ * 5 sezioni (TabView):
+ * 1. Template — lista (In uso / Archiviati separati) + anteprima PDF + editor form-based layout
+ * 2. Policy — singleton con dropdown categoria documento + labels in italiano
+ * 3. Regole — CRUD regole reminder PEC con kind parlante + help inline
+ * 4. Invio massivo — bulk-request-recompilation (dry-run + live)
+ * 5. Testi PEC — editor dei 5 template email AR1 (sync BE Gepafin via pull /internal/ar1-email-templates)
*/
const Ar1AdminConfig = () => {
const toast = useRef(null);
@@ -40,15 +78,18 @@ const Ar1AdminConfig = () => {
const [loadingTpl, setLoadingTpl] = useState(false);
const [editLayoutOpen, setEditLayoutOpen] = useState(false);
const [editLayoutTpl, setEditLayoutTpl] = useState(null);
- const [layoutJsonText, setLayoutJsonText] = useState('');
+ const [layoutForm, setLayoutForm] = useState({}); // form strutturato
+ const [layoutAdvancedJson, setLayoutAdvancedJson] = useState(''); // modalita JSON raw
+ const [useAdvancedEditor, setUseAdvancedEditor] = useState(false);
const [newVersionOpen, setNewVersionOpen] = useState(false);
const [newVersionVariant, setNewVersionVariant] = useState('A1');
- const [newVersionData, setNewVersionData] = useState({ version: '', layout_config: '{}', activate_now: true });
+ const [newVersionData, setNewVersionData] = useState({ version: '', activate_now: true });
// ========= POLICY =========
const [policy, setPolicy] = useState(null);
const [policyDraft, setPolicyDraft] = useState(null);
const [savingPolicy, setSavingPolicy] = useState(false);
+ const [docCategories, setDocCategories] = useState([]);
// ========= PEC RULES =========
const [pecRules, setPecRules] = useState([]);
@@ -63,24 +104,50 @@ const Ar1AdminConfig = () => {
const [bulkResult, setBulkResult] = useState(null);
const [bulkRunning, setBulkRunning] = useState(false);
+ // ========= EMAIL TEMPLATES =========
+ const [emailTemplates, setEmailTemplates] = useState([]);
+ const [loadingEmail, setLoadingEmail] = useState(false);
+ const [availableVariables, setAvailableVariables] = useState([]);
+ const [editEmailOpen, setEditEmailOpen] = useState(false);
+ const [editEmailData, setEditEmailData] = useState(null);
+ const [previewHtml, setPreviewHtml] = useState(null);
+ const [previewSubject, setPreviewSubject] = useState(null);
+
// ---- load all ----
const loadTemplates = () => {
setLoadingTpl(true);
Ar1Service.listTemplates(
(resp) => { setTemplates(resp?.items || resp || []); setLoadingTpl(false); },
- (err) => { setLoadingTpl(false); if (toast.current) toast.current.show({ severity: 'error', summary: 'Errore', detail: err?.detail || 'Load templates fallito' }); }
+ (err) => { setLoadingTpl(false); if (toast.current) toast.current.show({ severity: 'error', summary: 'Errore', detail: err?.detail || 'Caricamento template fallito' }); }
);
};
const loadPolicy = () => {
Ar1Service.getPolicy(
(resp) => { setPolicy(resp); setPolicyDraft(resp); },
- (err) => { if (toast.current) toast.current.show({ severity: 'error', summary: 'Errore', detail: err?.detail || 'Load policy fallito' }); }
+ (err) => { if (toast.current) toast.current.show({ severity: 'error', summary: 'Errore', detail: err?.detail || 'Caricamento policy fallito' }); }
);
};
const loadPecRules = () => {
Ar1Service.listPecSchedule(
(resp) => setPecRules(resp?.items || resp || []),
- (err) => { if (toast.current) toast.current.show({ severity: 'error', summary: 'Errore', detail: err?.detail || 'Load pec rules fallito' }); }
+ (err) => { if (toast.current) toast.current.show({ severity: 'error', summary: 'Errore', detail: err?.detail || 'Caricamento regole fallito' }); }
+ );
+ };
+ const loadDocCategories = () => {
+ Ar1Service.listDocumentCategories(
+ (resp) => setDocCategories(resp?.items || []),
+ (err) => console.warn('Categorie non caricate:', err)
+ );
+ };
+ const loadEmailTemplates = () => {
+ setLoadingEmail(true);
+ Ar1Service.listEmailTemplates(
+ (resp) => {
+ setEmailTemplates(resp?.items || []);
+ setAvailableVariables(resp?.available_variables || []);
+ setLoadingEmail(false);
+ },
+ (err) => { setLoadingEmail(false); if (toast.current) toast.current.show({ severity: 'error', summary: 'Errore', detail: err?.detail || 'Caricamento testi fallito' }); }
);
};
@@ -88,54 +155,106 @@ const Ar1AdminConfig = () => {
loadTemplates();
loadPolicy();
loadPecRules();
+ loadDocCategories();
+ loadEmailTemplates();
}, []);
- // ========= TEMPLATE HANDLERS =========
+ // ==================================================
+ // SEZIONE 1: TEMPLATE
+ // ==================================================
+
+ // Divido active vs archived per UI separata
+ const activeTemplates = templates.filter(t => t.status === 'ACTIVE' || t.status === 'DRAFT');
+ const archivedTemplates = templates.filter(t => t.status === 'ARCHIVED');
+
const openEditLayout = (tpl) => {
setEditLayoutTpl(tpl);
- setLayoutJsonText(JSON.stringify(tpl.layout_config || {}, null, 2));
+ const lc = tpl.layout_config || {};
+ // popolo form con chiavi comuni; altre restano in "advanced JSON"
+ setLayoutForm({
+ brand_name: lc.brand?.name || 'Gepafin S.p.A.',
+ brand_logo_url: lc.brand?.logo_url || '',
+ brand_color_primary: lc.brand?.color_primary || '#003d7a',
+ brand_color_accent: lc.brand?.color_accent || '#e65100',
+ header_title: lc.header?.title || 'Modulo AR1 — Adeguata Verifica',
+ header_subtitle: lc.header?.subtitle || 'D.Lgs. 231/2007',
+ intro_salutation: lc.intro?.salutation || 'Gentile Cliente,',
+ intro_body: lc.intro?.body || '',
+ privacy_url: lc.privacy?.url || '',
+ privacy_body: lc.privacy?.body || ''
+ });
+ setLayoutAdvancedJson(JSON.stringify(lc, null, 2));
+ setUseAdvancedEditor(false);
setEditLayoutOpen(true);
};
+ const buildLayoutFromForm = () => ({
+ brand: {
+ name: layoutForm.brand_name,
+ logo_url: layoutForm.brand_logo_url,
+ color_primary: layoutForm.brand_color_primary,
+ color_accent: layoutForm.brand_color_accent
+ },
+ header: {
+ title: layoutForm.header_title,
+ subtitle: layoutForm.header_subtitle
+ },
+ intro: {
+ salutation: layoutForm.intro_salutation,
+ body: layoutForm.intro_body
+ },
+ privacy: {
+ url: layoutForm.privacy_url,
+ body: layoutForm.privacy_body
+ }
+ });
+
const saveLayout = () => {
- let parsed;
- try { parsed = JSON.parse(layoutJsonText); }
- catch (e) { if (toast.current) toast.current.show({ severity: 'error', summary: 'JSON invalido', detail: e.message }); return; }
- Ar1Service.updateTemplateLayout(editLayoutTpl.id, parsed,
+ let payload;
+ if (useAdvancedEditor) {
+ try { payload = JSON.parse(layoutAdvancedJson); }
+ catch (e) { if (toast.current) toast.current.show({ severity: 'error', summary: 'JSON non valido', detail: e.message }); return; }
+ } else {
+ payload = buildLayoutFromForm();
+ }
+ Ar1Service.updateTemplateLayout(editLayoutTpl.id, payload,
() => {
- if (toast.current) toast.current.show({ severity: 'success', summary: 'OK', detail: 'Layout aggiornato' });
+ if (toast.current) toast.current.show({ severity: 'success', summary: 'Salvato', detail: 'Layout aggiornato' });
setEditLayoutOpen(false);
loadTemplates();
},
- (err) => { if (toast.current) toast.current.show({ severity: 'error', summary: 'Errore', detail: err?.detail || 'Save fallito' }); }
+ (err) => { if (toast.current) toast.current.show({ severity: 'error', summary: 'Errore', detail: err?.detail || 'Salvataggio fallito' }); }
);
};
const openNewVersion = (variant) => {
setNewVersionVariant(variant);
- setNewVersionData({ version: '', layout_config: '{}', activate_now: true });
+ setNewVersionData({ version: '', activate_now: true });
setNewVersionOpen(true);
};
const saveNewVersion = () => {
- let layoutParsed;
- try { layoutParsed = JSON.parse(newVersionData.layout_config); }
- catch (e) { if (toast.current) toast.current.show({ severity: 'error', summary: 'Layout JSON invalido', detail: e.message }); return; }
+ // Eredita layout_config dalla versione ACTIVE corrente
+ const active = templates.find(t => t.variant === newVersionVariant && t.status === 'ACTIVE');
+ const layoutConfig = active?.layout_config || {};
Ar1Service.createNewTemplateVersion(newVersionVariant, {
version: newVersionData.version,
- layout_config: layoutParsed,
+ layout_config: layoutConfig,
activate_now: newVersionData.activate_now
},
() => {
- if (toast.current) toast.current.show({ severity: 'success', summary: 'OK', detail: `Nuova versione ${newVersionVariant} v${newVersionData.version} creata` });
+ if (toast.current) toast.current.show({ severity: 'success', summary: 'Creata', detail: `Nuova versione ${newVersionVariant} v${newVersionData.version}` });
setNewVersionOpen(false);
loadTemplates();
},
- (err) => { if (toast.current) toast.current.show({ severity: 'error', summary: 'Errore', detail: err?.detail || 'Create fallito' }); }
+ (err) => { if (toast.current) toast.current.show({ severity: 'error', summary: 'Errore', detail: err?.detail || 'Creazione fallita' }); }
);
};
- // ========= POLICY HANDLERS =========
+ // ==================================================
+ // SEZIONE 2: POLICY
+ // ==================================================
+
const savePolicy = () => {
setSavingPolicy(true);
const patch = {
@@ -151,16 +270,19 @@ const Ar1AdminConfig = () => {
setSavingPolicy(false);
setPolicy(resp);
setPolicyDraft(resp);
- if (toast.current) toast.current.show({ severity: 'success', summary: 'OK', detail: 'Policy aggiornata' });
+ if (toast.current) toast.current.show({ severity: 'success', summary: 'Salvata', detail: 'Policy aggiornata' });
},
(err) => {
setSavingPolicy(false);
- if (toast.current) toast.current.show({ severity: 'error', summary: 'Errore', detail: err?.detail || 'Save policy fallito' });
+ if (toast.current) toast.current.show({ severity: 'error', summary: 'Errore', detail: err?.detail || 'Salvataggio fallito' });
}
);
};
- // ========= PEC RULE HANDLERS =========
+ // ==================================================
+ // SEZIONE 3: REGOLE PEC
+ // ==================================================
+
const openPecDialog = (rule) => {
if (rule) {
setPecEditing(rule);
@@ -174,7 +296,7 @@ const Ar1AdminConfig = () => {
});
} else {
setPecEditing(null);
- setPecDraft({ kind: '', offset_days: 0, is_recurring: false, recurring_interval_days: null, enabled: true, description: '' });
+ setPecDraft({ kind: '', offset_days: 30, is_recurring: false, recurring_interval_days: null, enabled: true, description: '' });
}
setPecDialogOpen(true);
};
@@ -182,307 +304,583 @@ const Ar1AdminConfig = () => {
const savePecRule = () => {
const payload = { ...pecDraft };
const onOk = () => {
- if (toast.current) toast.current.show({ severity: 'success', summary: 'OK', detail: pecEditing ? 'Regola aggiornata' : 'Regola creata' });
+ if (toast.current) toast.current.show({ severity: 'success', summary: 'Salvata', detail: pecEditing ? 'Regola aggiornata' : 'Regola creata' });
setPecDialogOpen(false);
loadPecRules();
};
- const onKo = (err) => { if (toast.current) toast.current.show({ severity: 'error', summary: 'Errore', detail: err?.detail || 'Save fallito' }); };
+ const onKo = (err) => { if (toast.current) toast.current.show({ severity: 'error', summary: 'Errore', detail: err?.detail || 'Salvataggio fallito' }); };
if (pecEditing) Ar1Service.updatePecRule(pecEditing.id, payload, onOk, onKo);
else Ar1Service.createPecRule(payload, onOk, onKo);
};
const deletePecRule = (rule) => {
confirmDialog({
- message: `Eliminare la regola "${rule.kind}"?`,
+ message: `Eliminare la regola "${PEC_KIND_LABEL[rule.kind] || rule.kind}"? Non sara piu inviata la PEC corrispondente.`,
header: 'Conferma eliminazione',
icon: 'pi pi-exclamation-triangle',
+ acceptLabel: 'Elimina',
+ rejectLabel: 'Annulla',
acceptClassName: 'p-button-danger',
accept: () => {
Ar1Service.deletePecRule(rule.id,
() => {
- if (toast.current) toast.current.show({ severity: 'success', summary: 'OK', detail: 'Regola eliminata' });
+ if (toast.current) toast.current.show({ severity: 'success', summary: 'Eliminata', detail: 'Regola eliminata' });
loadPecRules();
},
- (err) => { if (toast.current) toast.current.show({ severity: 'error', summary: 'Errore', detail: err?.detail || 'Delete fallito' }); }
+ (err) => { if (toast.current) toast.current.show({ severity: 'error', summary: 'Errore', detail: err?.detail || 'Eliminazione fallita' }); }
);
}
});
};
- // ========= BULK HANDLERS =========
+ // ==================================================
+ // SEZIONE 4: BULK
+ // ==================================================
+
const runBulk = (dryRun) => {
setBulkRunning(true);
const companyIds = bulkCompanyIds.trim()
? bulkCompanyIds.split(',').map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n))
: null;
- const payload = {
- dry_run: dryRun,
- only_expired: bulkOnlyExpired,
- only_missing: bulkOnlyMissing,
- company_ids: companyIds,
- };
+ const payload = { dry_run: dryRun, only_expired: bulkOnlyExpired, only_missing: bulkOnlyMissing, company_ids: companyIds };
Ar1Service.bulkRequestRecompilation(payload,
(resp) => {
setBulkRunning(false);
setBulkResult({ ...resp, was_dry_run: dryRun });
if (toast.current) toast.current.show({
severity: 'success',
- summary: dryRun ? 'Dry-run completato' : 'Bulk eseguito',
+ summary: dryRun ? 'Anteprima completata' : 'PEC inviate',
detail: `${resp.matched || 0} aziende matchate`
});
},
(err) => {
setBulkRunning(false);
- if (toast.current) toast.current.show({ severity: 'error', summary: 'Errore', detail: err?.detail || 'Bulk fallito' });
+ if (toast.current) toast.current.show({ severity: 'error', summary: 'Errore', detail: err?.detail || 'Invio fallito' });
}
);
};
- // ---------- RENDER ----------
- const tplStatusTpl = (row) => {
- const severity = row.status === 'ACTIVE' ? 'success' : row.status === 'ARCHIVED' ? 'secondary' : 'warning';
- return
- {__('Gestione template, policy, regole di scadenza e invio massivo PEC per il modulo di adeguata verifica.', 'gepafin')} + Gestione template, policy, regole di scadenza, invio massivo e testi PEC per il modulo antiriciclaggio (D.Lgs. 231/2007).
{__('Caricamento...', 'gepafin')}
} + {!policyDraft &&Caricamento…
} {policyDraft && ( -{__('Aziende matchate:', 'gepafin')} {bulkResult.matched ?? 0}
+Aziende matchate: {bulkResult.matched ?? 0}
{!bulkResult.was_dry_run && ( -{__('Form segnati per PEC:', 'gepafin')} {bulkResult.marked_for_pec ?? bulkResult.marked ?? 0}
+Form segnati per invio PEC: {bulkResult.marked_for_pec ?? bulkResult.marked ?? 0}
)} {bulkResult.company_ids && bulkResult.company_ids.length > 0 && ( -{__('ID:', 'gepafin')} {bulkResult.company_ids.join(', ')}
+ID aziende: {bulkResult.company_ids.slice(0, 30).join(', ')}{bulkResult.company_ids.length > 30 ? '…' : ''}
)}{'{{nome_variabile}}'}):