Nuovo modulo FE speculare a rendicontazione. Integrazione con microservizio
ar1-compiler (AX41:18091, 26 endpoint live, JWT HS512 condiviso con GEPAFIN-BE).
FILE CREATI (1159 LOC):
src/modules/ar1/service/ar1Service.js (166 LOC)
Client HTTP pattern 1:1 da rendicontazioneService.js. Metodi:
- getStatusForCompany (pubblico, per compliance modal)
- createDraft / getForm / listFormsForCompany / updateQuadri
- submitForSignature / deleteForm
- generatePdf / downloadPdfUnsigned / downloadPdfSigned
- uploadSignature (multipart) / reVerifySignature
- archiveToCompanyDocument (manuale, solitamente auto)
src/modules/ar1/components/Ar1StatusTag.js (26 LOC)
Badge PrimeReact Tag per 9 stati (MISSING/DRAFT/AWAITING_SIGNATURE/SIGNED/
VERIFIED/VALID/APPROACHING/EXPIRED/SUPERSEDED) con severity+icon specifici.
src/modules/ar1/components/Ar1ComplianceModal.js (137 LOC)
Dialog al login se azienda ha AR1 MISSING/EXPIRED (bloccante, no dismiss)
o APPROACHING (dismissable 24h via sessionStorage). CTA 'Compila ora'
naviga a /ar1. Da montare nel layout principale con <Ar1ComplianceModal
companyId={userCompanyId} />.
src/modules/ar1/pages/Ar1Home.js (248 LOC)
Pagina principale beneficiario. Card status con countdown + CTA dinamici
(Compila/Riprendi/Firma/Rinnova). DataTable storico con azioni per riga
(riprendi, firma, elimina, scarica firmato). Dialog scelta variante per
nuovo form (A1/A2/A3).
src/modules/ar1/pages/Ar1Wizard.js (372 LOC)
Wizard data-driven: legge schema_snapshot del form e genera step/field
dinamicamente. Un step PrimeReact Steps per ogni quadro. Auto-save onBlur
via PUT /quadri. 7 renderer type-aware:
- text/email (uppercase CF regex)
- textarea
- date (Calendar it-IT)
- checkbox
- radio (opzioni string o {label,value})
- enum (Dropdown)
- yes_no_with_note (RadioButton SI/NO + textarea condizionale)
Handler row_type per Quadro B titolari effettivi (array fino a max_rows).
Handler upload_slots per Quadro F allegati. Nested_full per Quadro C LR
e D esecutore con sezione 'Dettaglio aggiuntivo'.
Solo DRAFT editabile, AWAITING_SIGNATURE+ in sola lettura.
Submit finale invia PUT /quadri + PUT /submit-for-signature e naviga
a /ar1/signature/:id.
src/modules/ar1/pages/Ar1Signature.js (210 LOC)
Pagina firma:
Step 1: genera PDF + download unsigned (filename AR1_A1_da-firmare.pdf)
Step 2: FileUpload PDF firmato (.pdf PAdES o .p7m CAdES, 50MB max)
→ DocVerify call (toast 'Verifica in corso, fino a 60s')
→ 4 outcome con toast specifici:
VERIFIED → success + redirect Home
SIGNED_NOT_VERIFIED → warn 'verifica manuale'
SIGNED_DOCVERIFY_UNAVAILABLE → warn 'DocVerify down'
NO_SIGNATURE_DETECTED → error 'Firmare prima il PDF'
Card 'Dettagli verifica' con firmatario/CF/metodo/scadenza se VERIFIED.
INTEGRAZIONE (pattern identico a rendicontazione):
src/layouts/DefaultLayout/components/AppSidebar/index.js
Aggiunta voce sidebar:
label: 'Dichiarazione AR1', icon: 'pi pi-id-card', href: '/ar1', id: 22,
enable: intersection(permissions, ['APPLY_CALLS', 'APPLY_CONFIDI_CALLS']).length
src/routes.js
Import Ar1Home/Ar1Wizard/Ar1Signature.
3 route con pattern ruoli:
/ar1 → BENEFICIARY/SUPER_ADMIN: Ar1Home, altri: PageNotFound
/ar1/wizard/:formId → BENEFICIARY/SUPER_ADMIN: Ar1Wizard
/ar1/signature/:formId → BENEFICIARY/SUPER_ADMIN: Ar1Signature
.env
+ REACT_APP_AR1_API_URL=http://78.46.41.91:18091
+ REACT_APP_RENDICONTAZIONE_API_URL=http://78.46.41.91:18090
VALIDAZIONE:
8 file @babel/parser parse-check con plugin JSX: 8 OK / 0 FAIL.
PROSSIMI STEP (non in questo commit):
- Rinaldo integra Ar1ComplianceModal nel layout principale post-login
- Rinaldo deploya DocVerify sul server BFLOWS/Gepafin e configura
AR1_DOCVERIFY_URL nel microservizio ar1-compiler (senza DocVerify,
degrada gracefully a SIGNED senza VERIFIED)
- BE Spring Ar1AmendmentPoller (4.5h, bundle in /tmp/rinaldo-bundle-ar1.zip)
Completamento lato beneficiario dopo R3.A (IstruttoriaPratica completo).
Modulo rendicontazione speculare al pattern piattaforma FE.
==5 PATCH APPLICATE==
1. submitAmendmentResponse — callback-chain con upload response_document
Se l'utente ha selezionato un file nel FileUpload, dopo il submit del
testo chiama uploadResponseDocument. Se l'upload fallisce, il testo
resta salvato (toast warn). Success unificato via afterMutation.
2. Sezione amendments benef — filtro DRAFT
Il benef non deve vedere le bozze: le DRAFT vivono solo lato istruttore
finche non viene chiamato /send. Doppio filtro (count + map).
3. Sezione amendments benef — render HTML + metadata
Il request_text ora viene da Editor lato istruttore (HTML), quindi
serve dangerouslySetInnerHTML. Mostra inoltre response_days, badge
'Allegato istruttore presente' se amendment_document_path, badge
'Allegato inviato con la risposta' se response_document_path.
4. Dialog risposta — Editor rich text
Sostituita InputTextarea con Editor (primereact) coerente con il
pattern del lato istruttore. height=180px.
5. Dialog risposta — FileUpload response_document + visualizzazione allegato istruttore
- Header del dialog mostra: richiesta HTML, badge 'Istruttore ha
allegato un documento' se presente, scadenza con icona calendario
e response_days in testo di aiuto.
- Nuovo campo FileUpload basic (PDF max 10MB) agganciato a
amendDialog.response_file.
- Width dialog aumentato da 560px a 720px (coerente con
IstruttoriaPratica dialog create/edit).
==VALIDAZIONE==
@babel/parser JSX: 31 nodes, no errori. File 69148 chars.
==STATO COMPLESSIVO SOCCORSO ISTRUTTORIO v3==
Backend (rendicontazione-api): COMPLETO — da13ca7 R1 + 34c4a47 R2
Frontend (bflows-bandi-fe): COMPLETO — 4982df4 R3.A + questo commit
Documento integrazione Cecilia: TODO (prossima sessione)
==NEXT==
- Test E2E UI sandbox (crea DRAFT con allegato istruttore -> modifica ->
invia -> simula mark-pec-sent via SQL -> benef vede soccorso con badge
allegato -> benef risponde con response_file -> istruttore vede
response con badge e chiude)
- Scrivere /opt/docs/gepafin-rendicontazione-amendment-spec-per-BE.md
per Cecilia Moretti con: spec endpoint /internal (pending-pec,
pending-reminder, mark-pec-sent, mark-pec-failed), poller cron BE,
tenant routing hub=1 PEC Massiva + ProtocolService 65.108.55.96:8080,
hub=2 Mailgun. 5 domande aperte (classifica, SviluppUmbria PEC,
allegati protocollati, ruoli autorizzati, firma digitale response).
Dopo test browser ampio con Carlo sono emersi 3 cose importanti:
1. LOGICA: il BE Spring Gepafin non implementa sostituzione automatica
dei documenti stessa categoria — uploadFileForCompany fa saveAll puro,
nessun softdelete del precedente, nessun UNIQUE constraint su
(company_id, document_category_id). In sandbox risultavano 2 DURC
VALID attivi simultaneamente. Mitigazione picker: dedup client-side
per categoria, preferenza VALID > DUE, a parita id desc.
2. CATEGORIE: il BE ha 3 macro-tipi (CompanyDocumentTypeEnum):
COMPANY_DOCUMENT (azienda — DURC/Visura/Bilancio),
PERSONAL_DOCUMENT (amministratore/legale rappresentante — CI/CF/antiric),
APPLICATION_DOCUMENT (legato a specifica application).
Carlo aveva intuito giusto: admin vs azienda e la divisione personal vs
company. Il picker ora fa 2 chiamate al BE (default retituisce
COMPANY+APPLICATION, poi filter esplicito documentType=PERSONAL_DOCUMENT)
e unisce i risultati con dedup.
3. UX RIGA DOCUMENTO: il layout che avevo fatto (FileUploadCell + Tag
esterno + icon button) rompeva il flex causa nesting, tag e refresh
andavano a capo. Separati 3 casi semantici puliti:
- CASO A repository: riga custom con [icona pdf] filename [tag stato]
[button Cambia] [button Rimuovi] — tutto orizzontale
- CASO B upload PC: FileUploadCell standard (preview/download/refresh/delete)
- CASO C vuoto: 2 pulsanti Carica dal PC / Scegli dal repository
CAMBIAMENTI CompanyDocumentPicker.js (+197 -52):
- dedupByCategory(docs) con ranking STATUS+id
- doppia chiamata getCompanyDocuments (default + PERSONAL)
- nuova colonna Origine con badge colorato (blu=Azienda, viola=Admin)
- 3 pulsanti manuali per tab Origine (SelectButton PrimeReact aveva
issues styling col tema Gepafin: label bianche invisibili sui non-selected)
- ORIGIN_CFG con 3 varianti per COMPANY/PERSONAL/APPLICATION
CAMBIAMENTI PraticaRendicontazioneEdit.js (+55 -17):
- riga doc riscritta con 3 branch distinti per stato
- pulsanti outlined con label esplicite 'Cambia' / 'Rimuovi' (icon-only
text button avevano scopribilita bassa)
- handler Rimuovi collegato a deleteEntityFile esistente + toast feedback
Test browser verificato con Carlo: dedup ok (2 DURC in DB → 1 nel
picker), tabs Azienda/Amministratore leggibili, label pulsanti chiare,
flusso Cambia/Rimuovi funziona.
Su feedback di Carlo: la scelta del page size complica l'UI senza benefit
concreto per il caso d'uso (picker modale dove N doc tipicamente ~20-50).
Paginator ora: FirstPageLink PrevPageLink PageLinks NextPageLink
LastPageLink + counter '1-10 di N'. Righe fisse a 10.
Tre fix UX raggruppati sul CompanyDocumentPicker dopo feedback browser di Carlo:
1. Spacing banner informativo → filtri: aumentato a 1.5rem (era classe mb-3
PrimeReact che non applicava abbastanza spazio)
2. Icona lente ricerca non allineata col placeholder: p-input-icon-left
in PrimeReact 10 non aggiunge piu padding-left automatico. Sostituito
lo span wrapper con div position:relative + icona absolute left 1rem
pointer-events none zIndex 1, InputText paddingLeft 2.75rem. Portable
tra versioni PrimeReact, funziona indipendentemente da eventuali CSS
override.
3. Paginazione client-side sulla DataTable: paginator con default 10 righe,
rowsPerPageOptions [10, 25, 50], template full con navigation + counter
'1-10 di N'. Rimosso scrollable/scrollHeight 400px (la paginazione
sostituisce lo scroll infinito, UX piu prevedibile per liste grandi).
Sufficiente client-side per company con fino a qualche centinaio di
doc (PMI tipicamente 20-50). Se in futuro servira server-side paginated
dal BE Spring, e un evoluzione incrementale non bloccante.
Fix identificato testando: il BE Spring CompanyDocumentDao ha 2 filtri che
escludevano i doc seedati: status!=EXPIRED e type IN (COMPANY_DOCUMENT,
APPLICATION_DOCUMENT). Il campo 'type' nel BE e una macro-tipologia,
non il sotto-tipo (DURC/VISURA/etc) che vive in document_category_id.
Seed aggiornato (runtime): type='COMPANY_DOCUMENT' per tutti i doc,
e il sotto-tipo viene esposto via companyDocument.category.categoryName.
Picker rifatto:
- usa category.categoryName come tipo visibile/filtrabile (non piu type)
- filtro status con sole opzioni VALID/DUE (gli EXPIRED sono filtrati
dal BE e non appaiono mai qui — il gate submit li blocca comunque
via JOIN live sul microservizio)
- header con icona cartella + contatore X/Y documenti
- banner Message informativo sull esclusione EXPIRED
- filtri in grid CSS rigido (search full-width, 2 dropdown affiancati
2fr equal) per evitare stacking verticale indesiderato
- DataTable con stripedRows, icone file-pdf, scadenze monospace
- per status DUE: mostra 'scade tra N giorni'
- footer: 'Selezionato: NOME + tag' a sx, pulsanti a dx
Componente passato da 195 a 254 righe. Webpack compila pulito
(solo warning no-unused-vars preesistenti non miei).
Chiude la promessa UX del super_admin (testo editor: 'I documenti già in regola nel
repository della Company saranno riutilizzati automaticamente'). Nel benef, oltre al
classico upload dal PC, e ora possibile pescare documenti dal repository Gepafin
della company, ereditando filename/scadenza e status live (VALID/DUE/EXPIRED).
Nuovi componenti:
- CompanyDocumentPicker.js (195 righe): Dialog PrimeReact con filtri tipo/stato/testo,
DataTable con radio selection, semaforo tag VALID/DUE/EXPIRED, mostra scadenza
formattata IT, pulsante conferma disabilitato finche nulla e selezionato.
Servizio:
- RendicontazioneService.linkDocumentFromRepository(remDocId, companyDocId, cb, err)
chiama il nuovo endpoint microservizio POST .../document/{id}/link-from-repository.
Integrazione PraticaRendicontazioneEdit sezione 4 Documenti:
- 2 state + 2 handler nuovi: repoPicker {visible, docCode}, openRepositoryPicker,
closeRepositoryPicker, handleRepositoryPick (ensureDocRecord -> link -> toast).
- UI riga documento richiesto ora ha 2 pulsanti quando vuoto:
[pi-upload] Carica dal PC [pi-folder-open] Scegli dal repository
- Quando linked: accanto al FileUploadCell compare Tag semaforo con lo status del
sorgente (VALID=verde/DUE=giallo/EXPIRED=rosso) + pulsante cambia (ri-apre picker).
- CompanyDocumentPicker montato a fondo pagina, riceve practice.company_id +
currentSourceId per evidenziare la scelta gia fatta.
Webpack compila pulito (solo warning no-unused-vars preesistenti non miei).
Test E2E backend gia verdi nel commit backend 7c8de6a.
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