From 1116f96acf5b5d3f6ee5a58d48ff5d1ea3531d30 Mon Sep 17 00:00:00 2001 From: BFLOWS Date: Mon, 20 Apr 2026 23:20:41 +0200 Subject: [PATCH] =?UTF-8?q?feat(amendment):=20ROUND=203.B=20UI=20benef=20c?= =?UTF-8?q?ompleta=20=E2=80=94=20response=20upload=20+=20render=20HTML=20+?= =?UTF-8?q?=20dialog=20rich?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- .../pages/PraticaRendicontazioneEdit.js | 114 ++++++++++++++---- 1 file changed, 91 insertions(+), 23 deletions(-) diff --git a/src/modules/rendicontazione/pages/PraticaRendicontazioneEdit.js b/src/modules/rendicontazione/pages/PraticaRendicontazioneEdit.js index 24b539b..d82d554 100644 --- a/src/modules/rendicontazione/pages/PraticaRendicontazioneEdit.js +++ b/src/modules/rendicontazione/pages/PraticaRendicontazioneEdit.js @@ -360,10 +360,33 @@ const PraticaRendicontazioneEdit = () => { toast.current?.show({ severity: 'warn', summary: __('Risposta troppo corta', 'gepafin') }); return; } + const fileToUpload = amendDialog.response_file; + const amendmentId = amendDialog.amendment.id; + RendicontazioneService.respondAmendmentBeneficiary( - practiceId, amendDialog.amendment.id, amendDialog.responseText, - (resp) => { setAmendDialog({ visible: false, amendment: null, responseText: '' }); - afterMutation(__('Risposta inviata all\'istruttore', 'gepafin'))(resp); }, + practiceId, amendmentId, amendDialog.responseText, + (resp) => { + if (fileToUpload) { + RendicontazioneService.uploadResponseDocument(practiceId, amendmentId, fileToUpload, + () => { + setAmendDialog({ visible: false, amendment: null, responseText: '', response_file: null }); + afterMutation(__('Risposta trasmessa con allegato', 'gepafin'))(resp); + }, + (err) => { + // testo salvato ma upload fallito — avviso e ricarico + setAmendDialog({ visible: false, amendment: null, responseText: '', response_file: null }); + toast.current?.show({ + severity: 'warn', + summary: __('Risposta salvata, upload allegato fallito', 'gepafin'), + detail: err?.message || '' + }); + afterMutation(null)(resp); + }); + } else { + setAmendDialog({ visible: false, amendment: null, responseText: '', response_file: null }); + afterMutation(__('Risposta inviata all\'istruttore', 'gepafin'))(resp); + } + }, onMutationError); }; @@ -465,8 +488,8 @@ const PraticaRendicontazioneEdit = () => { )} - {/* SOCCORSO ISTRUTTORIO (se presente) */} - {practice.amendments && practice.amendments.length > 0 && (<> + {/* SOCCORSO ISTRUTTORIO (se presente — esclude DRAFT, visibile solo quando PEC partita) */} + {practice.amendments && practice.amendments.filter(a => a.status !== 'DRAFT').length > 0 && (<>

{__('Richieste di soccorso istruttorio', 'gepafin')}

@@ -474,7 +497,7 @@ const PraticaRendicontazioneEdit = () => { {__('L\'istruttore ha chiesto integrazioni o chiarimenti. Rispondi al più presto.', 'gepafin')}

- {practice.amendments.map(a => { + {practice.amendments.filter(a => a.status !== 'DRAFT').map(a => { const statusCfg = { AWAITING: { sev: 'warning', label: 'In attesa della tua risposta' }, RESPONSE_RECEIVED: { sev: 'info', label: 'Risposta inviata, in attesa di chiusura' }, @@ -484,25 +507,41 @@ const PraticaRendicontazioneEdit = () => { return (
-
+ background: a.status === 'AWAITING' ? 'var(--orange-50, #fff7ed)' : 'var(--surface-50)', + marginBottom: '0.75rem' }}> +
- - {__('Scadenza:', 'gepafin')} {new Date(a.deadline).toLocaleDateString('it-IT')} + + {__('Scadenza:', 'gepafin')} {new Date(a.deadline).toLocaleDateString('it-IT')} + {a.response_days ? ` (${a.response_days}gg dalla richiesta)` : ''}
{a.status === 'AWAITING' && (
{__('Richiesta istruttore:', 'gepafin')} -
{a.request_text}
+
+ {a.amendment_document_path && ( +
+ + {__('Allegato istruttore presente (disponibile via PEC)', 'gepafin')} +
+ )} {a.response_text && (<> {__('Tua risposta:', 'gepafin')} -
{a.response_text}
+
+ {a.response_document_path && ( +
+ + {__('Allegato inviato con la risposta', 'gepafin')} +
+ )} )}
@@ -911,23 +950,52 @@ const PraticaRendicontazioneEdit = () => { {/* ---------- DIALOG RISPOSTA SOCCORSO ---------- */} - setAmendDialog({ visible: false, amendment: null, responseText: '' })}> + onHide={() => setAmendDialog({ visible: false, amendment: null, responseText: '', response_file: null })}> {amendDialog.amendment && (
{ e.preventDefault(); submitAmendmentResponse(); }}> -
- {__('Richiesta istruttore:', 'gepafin')} -
{amendDialog.amendment.request_text}
+
+ + {__('Richiesta istruttore', 'gepafin')} + {amendDialog.amendment.response_days ? ` — ${__('hai', 'gepafin')} ${amendDialog.amendment.response_days} ${__('giorni per rispondere', 'gepafin')}` : ''} + +
+ {amendDialog.amendment.amendment_document_path && ( +
+ + {__('L\'istruttore ha allegato un documento (trasmesso via PEC)', 'gepafin')} +
+ )} +
+ + {__('Scadenza:', 'gepafin')} {new Date(amendDialog.amendment.deadline).toLocaleDateString('it-IT')} +
+
- - setAmendDialog(d => ({ ...d, responseText: e.target.value }))} - placeholder={__('Descrivi le integrazioni fornite, allegati caricati, chiarimenti...', 'gepafin')} /> + + setAmendDialog(d => ({ ...d, responseText: e.htmlValue || '' }))} + placeholder={__('Descrivi le integrazioni fornite, gli allegati caricati, i chiarimenti...', 'gepafin')} />
+ +
+ + {}} + onSelect={(e) => setAmendDialog(d => ({ ...d, response_file: e.files[0] || null }))} /> + + {__('Documento integrativo (DURC aggiornato, chiarimento contabile, ecc.)', 'gepafin')} + +
+
-