Layout precedente aveva div con inline-style grezzi, totali affastellati
in flex unico, righe tranche plain senza gerarchia visiva.
Redesign completo mantenendo identica la logica:
- Card PrimeReact con header strutturato (titolo bando + azienda/domanda + importo)
- StatTile sub-component (label uppercase 0.75rem + value bold 1.15rem, bordo
sinistro colorato slate/green/primary/gray)
- Progress bar percentuale cap utilizzato (verde <100%, rosso >=100%)
- TrancheRow sub-component con icona circolare T1/T2 e icone
pi-file/users/paperclip per metadati
- Tag stato con icona (pencil/send/eye/check-circle/times-circle/exclamation-triangle)
- Divider PrimeReact tra corpo e footer
- Empty state tranches con message informativo su surface-50
- Dialog avvio tranche con box riepilogativo colorato
Il file era stato scritto su disco in sessione precedente ma non committato.
Committo ora prima della chiusura sessione.
Runtime TypeError cliccando ▸ per espandere le note di una fattura:
'(collection || []).findIndex is not a function'
Causa: la DataTable fatture usa rowGroupMode='subheader' + groupRowsBy.
In questa modalita PrimeReact chiama expandedRows.findIndex(...) al toggle
della row. Il valore iniziale era {} (oggetto) — findIndex non esiste
sull'oggetto → crash alla prima espansione.
Fix: useState([]) invece di useState({}). Il reducer di onRowToggle
gestisce array/oggetto trasparentemente, ma il bootstrap deve essere
un array quando c'e rowGroupMode.
La tabella ULA (expandedUla) resta {} perche non usa subheader e
PrimeReact accetta entrambi i formati in quella configurazione.
Segnalazione Carlo (demo in corso).
La sezione 'Verifica fatture' appariva piu stretta di 'Verifica dipendenti ULA'
in istruttoria. La tabella ULA era wrappata in <div style={width:100%}> mentre
quella fatture no: il componente DataTable di PrimeReact non eredita
sempre la width del parent appPageSection quando e figlio diretto di una
IIFE senza container intermedio.
Fix:
- aggiunto style={{ width: '100%' }} alla prop top-level della DataTable fatture
(si applica al wrapper .p-datatable, non solo al <table> interno)
- aggiunto wrapper <div style={{ width: '100%' }}> attorno alla DataTable
fatture, coerente con la struttura gia usata dalla sezione ULA
Nessuna modifica al contenuto o alle colonne.
Segnalazione Carlo.
Due bug correlati risolti:
1. userRole era sempre null. Il codice usava storeGet('getUser') che non esiste
nel selectors (sono solo getToken, getRole, getPermissions). Sostituito con
storeGet('getRole') che ritorna userData.role.roleType.
Effetto: canUseManagerView e sempre false, toggle manager mai visibile,
il capo istruttore vede solo 'Nessuna pratica in coda'.
2. managerMode partiva a false anche per manager e superadmin. Ora default
true per ROLE_INSTRUCTOR_MANAGER e ROLE_SUPER_ADMIN: partono in vista
'tutte le pratiche' con possibilita riassegnare.
Rimosso anche import useMemo non piu usato.
Segnalazione Carlo: 'qualcosa non quadra nel profilo del capoistruttore'.
La pagina 'Le mie rendicontazioni' usava div con style inline (border, padding,
background white) e classi inventate/flex wrapper custom che ignoravano il sistema
CSS di Gepafin (appPage, appPageSection, appPageSection__withBorder, __list,
__listItem, __pMeta, __actions) usato ovunque altrove (Dashboard, Bandi, Domande).
Risultato: card piena larghezza schermo, tipografia incoerente, spaziature
al 100% invece di contenute dentro il pattern standard.
Riscritta usando esclusivamente le classi di /assets/scss/components/appPage.scss:
- appPage + appPage__pageHeader (wrapper + header con bordo sinistro colorato)
- appPage__spacer per distacco
- wrapper esterno appPageSection (ora le card sono contenute dentro la sezione)
- ogni application-card e' un appPageSection__withBorder (bordo 1px + radius 6
+ padding 17px gia stilati) con .row interno flex + .rowContent
- metriche cap/approvato/disponibile/tranches usano appPageSection__pMeta
(2 span, il secondo a sinistra/destra) invece di flex custom
- lista tranches usa ul.appPageSection__list + li.appPageSection__listItem.row
che ha gia padding 15px + border-bottom gestiti dallo scss
- icone + tag in appPageSection__iconActions
- bottoni nuova-tranche in appPageSection__actions con justify-content:flex-end
- empty state in appPageSection__withBorder centrato
- colori CSS variables (var(--primary-color), var(--text-color-secondary),
var(--green-700), var(--button-secondary-borderColor)) — niente colori inline
Nessuna modifica alla logica (load, openStartDialog, confirmStart, nav).
Dialog 'Avvia nuova tranche' invariato (gia era OK con appForm__field).
Sostituisce il vecchio flusso che proponeva solo 'Inizializza con template RE-START'.
Quando il bando non ha ancora uno schema, mostra un picker a 3 card:
1. 'Nuovo schema' -> inizializzazione blank (scheletro vuoto da popolare)
2. 'Da template' -> dropdown con template predefiniti + descrizione
3. 'Clona da bando' -> dropdown bandi con schema esistente
Nuovo componente: components/SchemaTemplatePicker.js (200 righe).
Gestisce:
- loading parallelo di templates + clonable-calls
- selezione card con border highlight primary-color
- dropdown espanso solo sulla card attiva (stopPropagation su click)
- bottone Inizia disabilitato finche la selezione non e completa
- spinner durante init, callback onInitialized con schema_json per aggiornare
il form dell'editor senza reload pagina
Nuovo service esportato: schemaPickerService { listTemplates, listClonableCalls,
initializeSchema(callId, payload) }.
BandoRendicontazioneSchemaEdit.js: rimosso il box 'Inizializza con template RE-START',
sostituito con <SchemaTemplatePicker /> quando !hasSchema. onInitialized popola
setSchemaRecord + setForm + mostra toast di conferma. Funzione handleInitializeRestart
resta nel file (non ancora chiamata, per sicurezza rollback).
IstruttoriaPratica.js:
- previewDialog esteso con entityType/entityId (non piu solo filename)
- openPreview/closePreview/doDownload rimpiazzano openPreview/downloadStub stub
- Dialog placeholder 'anteprima simulata' rimosso, sostituito con <FilePreviewDialog/>
- Bottoni anteprima/scarica in fatture/ULA/documenti usano gli endpoint reali
(disabled se !storage_path)
- Nuovi bottoni 'Anteprima verbale' (HTML tab) e 'Scarica verbale PDF'
nella toolbar per status in UNDER_REVIEW/AWAITING_AMENDMENT/APPROVED/REJECTED
- downloadVerbale/openVerbaleHtml helpers
PraticaRendicontazioneEdit.js:
- previewDialog state + openPreview/closePreview
- updateInvoiceFile/updateUlaFile/updateDocFile: aggiornano lo stato locale
dopo upload/delete senza full reload pagina
- ensureDocRecord: auto-crea RemissionDocument (via upsertDocument con filename=null)
prima dell'upload cosi FileUploadCell ha un entityId valido
- Colonne 'Allegato' nelle DataTable fatture/ULA ora renderizzano <FileUploadCell/>
con onPreview/onChange wired
- Sezione documenti: FileUploadCell per record esistenti, bottone 'Carica'
per record non ancora creati
- Modal fattura: rimosso campo 'Nome file PDF (simulato)', infobox post-save guida
al caricamento dalla tabella
- Modal dipendente: rimosso campo 'Nome file allegato (simulato)', infobox analogo
- <FilePreviewDialog/> montato in chiusura
Test JSX: @babel/parser OK su entrambi i file. Webpack ricompila hot-reload.
- components/FilePreviewDialog: Dialog full-height con iframe,
blob URL autenticato (fetch + Bearer token), revoca URL alla chiusura,
bottone scarica, usato da istruttore e beneficiario
- components/FileUploadCell: cella compatta per righe DataTable,
stati nessun-file/uploading/caricato, upload drag&drop (accept .pdf/.jpg/.png max 15MB),
preview/download/sostituisci/elimina con conferma confirmPopup, readOnly per istruttore
- service: 4 nuovi metodi file (uploadEntityFile multipart senza Content-Type forzato,
deleteEntityFile, fetchEntityFileBlob con parse Content-Disposition,
downloadEntityFile con anchor tag e revoke URL)
- service: 2 metodi verbale (downloadVerbale blob PDF,
openVerbaleHtml apre HTML in nuova tab per preview rapida)
Nessun pdf.js, solo iframe nativo + ObjectURL. Zero dipendenze aggiuntive.
Refactor completo della UI istruttore su pattern Excel-like dichiarato/verificato.
Editor schema bando (BandoRendicontazioneSchemaEdit):
- Nuovo dropdown 'Base di calcolo ammissibile' (imponibile/totale/regime-dependent)
- Nuovo Calendar 'Inizio periodo' accanto al period_start_rule esistente
IstruttoriaPratica — refactor totale:
- FATTURE: 1 sola DataTable con rowGroupMode='subheader' raggruppato per B1/B2/B3,
header colorato per categoria con totali dichiarato/ammesso live
- Colonne inline editabili: 'Imponibile ammesso' con InputNumber + save onBlur.
Stato auto-calcolato: = dichiarato -> AMMESSA; 0 < x < dichiarato -> PARZIALE;
x == 0 -> RESPINTA
- Label dinamiche 'Imponibile' vs 'Totale' in base a use_taxable_only
- Riga espandibile (pi-chevron) con textarea note istruttore + dettaglio IVA/totale
- Toggle icon ✓: se AMMESSA -> PENDING; altrimenti -> AMMESSA
- Toggle icon ✗: se RESPINTA -> PENDING; altrimenti -> RESPINTA
- Tooltip dinamici 'Conferma' / 'Annulla conferma'
- Badge rosso automatico 'Data fuori periodo' su invoice_in_period=false
ULA: stesso pattern inline (FTE dichiarato vs FTE ammesso) con header-box
manuale SOPRA la DataTable (non rowGroupMode, un solo gruppo) e forzatura
tableStyle width:100% per allineamento perfetto con fatture.
Documenti: lista con toggle ✓ VALIDO ↔ PENDING, ✗ NON_VALIDO/SCADUTO via dialog.
Performance critica — NO FULL RELOAD su verify:
- saveInvoiceInline/saveUlaInline/quickVerifyDoc/saveDocNote ora fanno
setBundle() con update locale della singola riga
- refreshGateOnly() ricarica solo il gate_check (totali) in background
- Eliminato il load() completo che faceva sfarfallare la pagina
Banner arancione automatico quando status=SUBMITTED: 'Pratica non presa in
carico' con CTA 'Prendi in carico'.
Bugfix:
- Rimossi import inutilizzati (InputText, isNil)
- Aggiunti import DataTable, Column
UX testata su NAPOLI SAS: 5 fatture 3 categorie, 2 ULA, 4 docs.
Totali si aggiornano live, toggle funzionanti, nessuno sfarfallio.
Replica il workflow del foglio Excel originale dell'istruttoria Gepafin.
Pattern preso da DomandaEditPreInstructor/components/ListOfFiles.
Pagina IstruttoriaPratica riscritta (858 righe):
RIEPILOGO FINANZIARIO esteso:
- Totale dichiarato (dal beneficiario)
- Totale verificato (somma AMMESSA + PARZIALE istruttore)
- Cap remissione (min(50% erogato, 12500))
- Remissione da riconoscere (da verificato)
- Residuo da restituire (erogato - remissione)
VERIFICA FATTURE per categoria con appPageSection__list:
- Ogni fattura come row con numero, fornitore, date, descrizione, Tag stato
- Tag rosso 'Date fuori periodo' se invoice_in_period=false O payment_in_period=false
- Riga dichiarato + riga verificato (se presente)
- Note rettifica evidenziate con barra arancione
- 5 pulsanti icona: eye (anteprima PDF) + download + pencil (rettifica con dialog)
+ thumbs-up AMMESSA + thumbs-down RESPINTA
- Thumbs up/down = ammissione/rifiuto rapido senza rettifica
- Pencil = dialog con dichiarato readonly + verificato editabile + note obbligatorie -> PARZIALE
VERIFICA ULA:
- Stesso pattern: eye/download/pencil/up/down
- Rettifica FTE (0-1) con note
VERIFICA DOCUMENTI:
- eye/download/thumbs-up VALIDO
- clock SCADUTO (apre dialog con motivazione)
- thumbs-down NON_VALIDO (apre dialog con motivazione)
VERBALE ISTRUTTORIA finale (visibile in UNDER_REVIEW/AWAITING_AMENDMENT):
- 3 checkbox: documentazione completa, ULA>1, erogato in range
- Textarea note sintetiche con save onBlur
Approva disabilitato finché tutte le righe hanno status != PENDING.
Anteprima PDF: dialog con placeholder sandbox (file reale sarà in prod).
Download: toast stub (in prod scarica dal storage).
- Nuova pagina RendicontazioniMie: dashboard beneficiario con pratiche esistenti
+ applications CONTRACT_SIGNED ready_to_start in tabella unificata
- Nuova pagina PraticaRendicontazioneEdit: form compilazione completo
+ riepilogo finanziario live (erogato, totale, cap, remissione spettante)
+ requisiti per invio con semafori live (gate check refresh on mount)
+ sezione regime IVA con update inline
+ fatture per categoria con dialog add + tabella + delete (per B1/B2/B3)
+ dipendenti ULA con dialog add (CF, contratto, FTE, periodo, allegato)
+ documenti richiesti con upload simulato (prompt nome file)
+ submit con confermazione, disabilitato finche' gate non passa
- Nuova pagina DevSwitchUser: impersonate sandbox-only per superadmin
- Voce sidebar "Le mie rendicontazioni" per ROLE_BENEFICIARY
- Voce sidebar "Dev: cambia utente" per ROLE_SUPER_ADMIN
- Service esteso con 12 metodi pratiche + impersonate
- Aggiunta voce 'Rendicontazione' in AppSidebar (id 21, icon pi-receipt)
- Nuova pagina RendicontazioneHome: dashboard con tabella bandi + stato schema
(Non creato / Bozza / Pubblicato) + azioni Crea/Modifica per ciascuno
- Nuova pagina BandoRendicontazioneSchemaEdit: form strutturato 6 sezioni
(importi/periodo, IVA, categorie, ULA, documenti, regole gate) con
salva bozza + pubblica, read-only dopo pubblicazione
- Nuovo service modules/rendicontazione/service/rendicontazioneService.js
(client fetch verso rendicontazione-api, JWT dallo store Zustand)
- 2 nuove route /rendicontazione e /bandi/:id/rendicontazione-schema
(gate su ROLE_SUPER_ADMIN)
- Bottone 'Schema rendicontazione' aggiunto in BandoEdit come shortcut
- Patch NotificationsSidebar per disabilitare WSS se REACT_APP_ENABLE_WEBSOCKET=0
(evita errori CORS in sandbox senza RabbitMQ)
UI coerente col codebase: appPage/appPageSection/appForm/appForm__cols/
fieldsRepeater, p-fluid per width input, h1+p in header con border-left