I due 'tab' interni al Dialog erano <Button> con prop text=true quando non selezionati:
rendering senza sfondo ne bordo, praticamente invisibili. Carlo non vedeva come
raggiungere l'editor Struttura quadri e pensava mancasse.
Sostituito l'hacky Button-tab con un vero TabView PrimeReact (stesso componente
del TabView esterno delle 5 tab). Due TabPanel:
1. 'Layout grafico' (icona pi pi-palette) - contenuto form brand/header/intro/privacy
2. 'Struttura quadri' (icona pi pi-list, + ' •' se modificata) - rende QuadriStructureEditor
Mapping activeIndex<->editSection:
editSection='layout' -> activeIndex=0
editSection='struttura' -> activeIndex=1
onTabChange setta entrambi.
Errore runtime al click 'Modifica' sul template: avevo chiamato Ar1Service.getTemplate
ma nel service esistente il metodo si chiama getTemplateDetail. Inoltre getNextVersion
non era mai stato aggiunto al service (il mio replace precedente non ha matchato la
signature di listTemplates che include un terzo parametro queryParams).
Fix:
- openEditTemplate ora chiama Ar1Service.getTemplateDetail (metodo esistente)
- Aggiunto Ar1Service.getNextVersion(variant) che chiama GET /admin/ar1-templates/
:variant/next-version (endpoint BE gia live)
Strada A: il superadmin puo modificare TUTTO via UI (layout grafico + struttura
dei quadri). Se tocca solo il layout -> PUT in place. Se tocca la struttura ->
il BE auto-bumpa la patch e archivia la versione precedente. I form gia compilati
continuano a usare il loro snapshot.
Nuovo componente:
src/modules/ar1/components/QuadriStructureEditor.js (438 LOC)
- Metadati snapshot: variant_label, legal_ref, normative_frame, variant_description
- Lista quadri in Accordion, per ogni quadro:
* id / title / description modificabili
* reorder su/giu, elimina quadro, aggiungi quadro
* Warning 'NORMATIVO' per Quadro G (is_legal_frame=true)
* Fields normali: editor per-campo con id, label, tipo (7 types), required,
max_length, pattern regex, options (per enum/radio), tag prefill_from
* Row fields (row_type, Quadro B titolari): sezione separata con warning
* Nested_full fields (Quadro C/D): sezione separata
* Upload slots (Quadro F): tag readonly (edit avanzato tbd)
- FIELD_TYPE_OPTIONS: text, email, date, checkbox, radio, enum, yes_no_with_note
- Usa Accordion multi-open per navigare piu quadri, Tag per metadati visuali
Cambiamenti in Ar1AdminConfig.js:
- Rimossi: openEditLayout, openNewVersion, saveNewVersion, stati newVersionOpen/
Data/Variant, Dialog 'Nuova versione' manuale (user sceglieva version semver)
- Aggiunti: openEditTemplate (carica template completo via GET detail),
saveEditTemplate (fa diff questions_snapshot, se cambiato chiama
createNewTemplateVersion senza version -> BE auto-bump, se invariato chiama
updateTemplateLayout in place), questionsStructureChanged helper (deep-equal
via JSON.stringify su clone deep fatto al load)
- Service: + getTemplate + getNextVersion (per preview numero versione)
- Bottoni azioni tab Template: solo 'Anteprima' + 'Modifica' (rimosso '+ Nuova vers.')
- Dialog unificato 1100px maximizable:
* Bar top con Tag variante/version/status + Message warning se struttura
modificata (mostra prossima versione preview es. v1.2.968)
* 2 tab interni (pulsanti custom): 'Layout grafico' vs 'Struttura quadri'
con indicatore • se struttura ha modifiche
* Sezione Layout: form come prima (brand/header/intro/privacy) + toggle JSON raw
* Sezione Struttura: rende QuadriStructureEditor
* Footer sticky: tag stato ('update in place' verde vs 'nuova versione' giallo)
+ bottone Salva che cambia label e severity: 'Salva layout' default vs
'Crea versione v1.2.968' warning quando struttura cambiata
- Dialog 'Nuova versione' rimosso (mai piu input manuale di semver)
Ora il toast error mostra es. 'Versione: deve essere nel formato X.Y.Z
(esempio: 1.1.0)' invece di 'version: String should match pattern
^\d+\.\d+\.\d+$'.
2 nuove utility:
- FIELD_LABELS_IT: mapping field name -> label IT (version -> 'Versione',
kind -> 'Tipo regola', subject -> 'Oggetto', body_html -> 'Corpo HTML',
validity_days -> 'Validita', ecc. — 20 campi mappati)
- translatePydanticMsg(msg, type, ctx): riconosce i type Pydantic comuni:
* string_pattern_mismatch + ctx.pattern semver -> 'deve essere nel
formato X.Y.Z (esempio: 1.1.0)'
* missing -> 'campo obbligatorio mancante'
* string_type / int_type / bool_type -> 'deve essere stringa/intero/bool'
* greater_than_equal / less_than_equal -> 'deve essere almeno N / al
massimo N' (usando ctx.ge / ctx.le)
* string_too_short / too_long -> 'troppo corto/lungo (min/max N caratteri)'
* value_error -> rimuove il prefisso 'Value error, '
* fallback: msg originale (non rompe nulla per casi non mappati)
formatErrorDetail ora usa entrambi: estrae l'ultimo loc (field name piu
preciso), lo traduce via FIELD_LABELS_IT, concatena col msg tradotto.
Bug: click 'Crea versione' con version vuota o invalida -> pagina bianca + 'error'.
Causa: il BE restituisce 422 con detail come ARRAY Pydantic [{loc, msg, type}].
Il codice faceva detail: err?.detail || 'fallback' -> array passato a PrimeReact Toast
-> React render 'Objects are not valid as a React child' -> crash unhandled.
Fix: helper formatErrorDetail(detail, fallback) che normalizza:
- string -> ritorna direttamente
- array Pydantic -> 'loc.loc: msg; loc.loc: msg' (filtrato 'body')
- object -> JSON.stringify
- altro -> String(x)
Applicato con regex a tutti i pattern 'err?.detail || "fallback"' nel file (tutti
i toast show di errore nei vari handler: saveLayout, saveNewVersion, savePolicy,
savePecRule, deletePecRule, saveEmail, runPreview, runBulk, loadXxx).
Il tentativo precedente usava la classe 'mr-2' (convenzione Primeflex), ma
grep conferma che primeflex NON e incluso nel progetto (nessun import in src/,
assente da package.json). 'mr-2' era classe morta → icone restavano attaccate.
Pattern corretto del progetto (vedi Ar1ComplianceModal.js:73, SchemaTemplatePicker.js:111):
<i className="pi pi-X" style={{ marginRight: '0.5rem' }} />
Soluzione: TabPanel 'leftIcon' stringa -> 'header' JSX con icona+label:
header={<span><i className="pi pi-X" style={{marginRight:'0.5rem'}} />Label</span>}
Applicato ai 5 TabPanel (Template / Policy / Regole reminder / Invio massivo /
Testi comunicazioni).
PrimeReact leftIcon non ha margin-right di default, le icone erano incollate
al testo ('Template', 'Policy', etc). Aggiunta classe utility mr-2
(margin-right 0.5rem) su tutti e 5 i TabPanel, pattern identico a quello
usato in rendicontazione (es. IstruttoriaPratica.js linea con
leftIcon='pi pi-file mr-2').
I 7 Tag delle variabili disponibili (company_name, company_piva, ar1_form_url,
expires_at, days_to_expiry, variant, signer_name) sono ora CLICCABILI dentro
il dialog di modifica testo. Click inserisce {{variabile}} nel campo attivo
al punto del cursore (o in coda se focus perso).
Cambiamenti:
- Stato nuovo: activeEmailField ('subject' | 'body_html' | 'body_text'),
inizialmente 'body_html'
- 3 ref: subjectInputRef, bodyHtmlInputRef, bodyTextInputRef
- Funzione insertVariable(varName):
1. Legge editEmailData[activeEmailField]
2. Calcola posizione cursore via ref.getInput().selectionStart (fallback: coda)
3. Inserisce '{{' + varName + '}}' al cursore
4. Riposiziona cursor dopo il token inserito (setTimeout 0 per React re-render)
- 3 campi Input wrappati con ref + onFocus={() => setActiveEmailField(...)}
- Box variabili spostato DENTRO il dialog (prima era nel TabPanel, poco scopribile).
Mostra live quale campo e attivo: 'inserite in: Oggetto / Corpo HTML / Corpo testo'
- Tag stile severity=info + cursor:pointer + userSelect:none per feedback visuale
- Box variabili nel tab ora e un semplice Message informativo (la funzione e nel dialog)
UX: l'utente clicca dentro Oggetto, poi clicca {{company_name}}, vede il token
comparire al cursore. Se clicca Corpo HTML, poi un'altra variabile, va in quel
campo. Nessun drag&drop, nessuna complicazione.
Errore: avevo usato 'rounded text' (senza bordo) o label esplicita (fuori standard progetto).
Carlo ha giustamente chiesto di copiare il pattern esistente. Pattern autentico delle
DataTable azioni in bflows-bandi-fe, verificato in rendicontazione/IstruttoriaPratica.js:
<Button icon="pi pi-..." rounded outlined size="small" severity="..."
tooltip="..." tooltipOptions={{ position: 'top' }}
onClick={...} />
Cambiamenti:
- pecActionsTpl: icon matita severity=info + icon cestino severity=danger, tooltip, gap 0.25rem
- tplActiveActionsTpl: icon occhio info + matita default + plus warning, tooltip su ciascuno
- emailActionsTpl: icon matita info (era Button con label fuori standard), wrappato in div
- colonna pecActionsTpl width 200→130 (icon-only non serve spazio)
- colonna tplActiveActionsTpl width 280→170 (stessa logica)
Icone sono primeicons (pi pi-*), NON lucide. In tutto il progetto si usa primeicons
(confermato grep globale: zero import lucide-react nel FE bflows-bandi-fe).
Riscrittura completa di Ar1AdminConfig.js (490 -> 888 LOC) con UI italianizzata,
labels parlanti, editor form-based per layout template, tab 'Testi comunicazioni'
con editor dei 5 template email + anteprima server-side.
CAMBIAMENTI FUNZIONALI:
Tab 1 Template:
- 'In uso' (ACTIVE/DRAFT) vs 'Archiviati' ora in DUE Card separate, non mescolati
- Nomi varianti in italiano ('A1 — Persona Giuridica (societa, ente)', ecc)
- Status tag italiano ('In uso', 'Archiviato', 'Bozza')
- Editor layout: DEFAULT modalita form (Brand/Header/Intro/Privacy con campi
espliciti nome, logo_url, colori primario+accento, titoli, saluto, corpo
introduttivo, URL privacy, testo piede). Toggle 'Modalita avanzata (JSON raw)'
per chi vuole editare tutto il layout_config.
- Bottone 'Anteprima PDF' presente (placeholder toast TODO — endpoint BE da wirare)
- Bottone 'Nuova versione' eredita automaticamente layout_config da ACTIVE corrente
Tab 2 Policy:
- Tutti i label tradotti in italiano con help text inline per ogni campo
- Dropdown 'Categoria documento aziendale' da GET /admin/document-categories
(cross-schema read a gepafin_schema.document_category) invece di InputNumber
raw. Mostra 'DURC — Documento Unico...', 'ANTIRICICLAGGIO — Dichiarazione...'
- Switch con descrizioni espanse (cosa fa, quando si attiva)
- Divider visivo tra campi numerici e switch booleani
Tab 3 Regole reminder:
- Colonna 'Regola' con label italiano parlante + kind tecnico in sottotitolo
- Colonna 'Quando parte' calcolata dinamicamente: '30 giorni PRIMA',
'Il giorno della scadenza', '5 giorni DOPO', ecc
- Colonna 'Ricorrenza' formattata ('una tantum' vs 'ogni 30 giorni')
- Dialog edit: Dropdown PEC_KIND_OPTIONS con 5 etichette italiane (kind
disabled se editing esistente), help text inline sul campo offset_days
che cambia live ('3 giorni prima della scadenza' / 'giorno della scadenza'
/ '3 giorni dopo la scadenza')
Tab 4 Invio massivo:
- Label italiano 'Solo aziende con AR1 scaduta' / 'Solo aziende senza AR1'
- Pulsante 'Anteprima (non invia)' con toast descrittivo
- Pulsante 'Invia PEC' richiede ConfirmDialog
- Messaggio warning giallo chiarisce che la PEC sara dispatchata dal BE Gepafin
- Box esito con matched / marked_for_pec / company_ids (trimmato a 30+…)
Tab 5 Testi comunicazioni (NUOVO):
- Banner info + elenco variabili supportate come Tag cliccabili (7 variabili)
- DataTable 5 righe: Tipo comunicazione (label IT + kind mono) / Oggetto /
Versione (Tag 'v1', 'v2', ...) / Aggiornato il / Azione 'Modifica'
- Dialog edit massimizzabile: subject + body_html (textarea monospace 10
righe) + body_text fallback (5 righe) + note interne
- Bottone 'Anteprima (dati di esempio)' chiama POST /admin/ar1-email-templates/
{kind}/preview e mostra rendering HTML interpolato (dangerouslySetInnerHTML)
con subject renderizzato + body in box stile email
- Save bump version lato BE (toast 'Testo aggiornato (version N)')
SERVICE:
ar1Service.js esteso da 213 -> 247 LOC:
+ listDocumentCategories (GET /admin/document-categories)
+ listEmailTemplates / getEmailTemplate / updateEmailTemplate /
previewEmailTemplate (4 metodi admin email)
VALIDAZIONE:
Parse-check @babel/parser plugin JSX: 2/2 OK (service + Ar1AdminConfig).
Hot-reload CRA webpack compiled with 1 warning (solo unused-vars pre-esistenti).
COSE NON ANCORA FATTE (next):
- Endpoint BE POST /admin/ar1-templates/:id/preview per anteprima PDF
(wiring FE: rimuovere toast TODO dentro openEditLayout/tplActiveActionsTpl)
- Test manuale dal browser con hard-refresh