feat(rendicontazione): editor schema con form strutturato + dashboard + integrazione microservizio
- 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
This commit is contained in:
145
src/modules/rendicontazione/pages/RendicontazioneHome.js
Normal file
145
src/modules/rendicontazione/pages/RendicontazioneHome.js
Normal file
@@ -0,0 +1,145 @@
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
// components
|
||||
import { Button } from 'primereact/button';
|
||||
import { DataTable } from 'primereact/datatable';
|
||||
import { Column } from 'primereact/column';
|
||||
import { Card } from 'primereact/card';
|
||||
import { Tag } from 'primereact/tag';
|
||||
import { Skeleton } from 'primereact/skeleton';
|
||||
import { Toast } from 'primereact/toast';
|
||||
|
||||
// api
|
||||
import BandoService from '../../../service/bando-service';
|
||||
import RendicontazioneService from '../service/rendicontazioneService';
|
||||
|
||||
|
||||
const SCHEMA_STATUS_CONFIG = {
|
||||
null: { severity: 'info', label: __('Non creato', 'gepafin'), icon: 'pi pi-circle' },
|
||||
DRAFT: { severity: 'warning', label: __('Bozza', 'gepafin'), icon: 'pi pi-pencil' },
|
||||
PUBLISHED: { severity: 'success', label: __('Pubblicato', 'gepafin'), icon: 'pi pi-check-circle' }
|
||||
};
|
||||
|
||||
|
||||
const RendicontazioneHome = () => {
|
||||
const navigate = useNavigate();
|
||||
const toast = useRef(null);
|
||||
|
||||
const [rows, setRows] = useState([]); // {bando, schema}
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const loadData = () => {
|
||||
setLoading(true);
|
||||
BandoService.getBandiPaginated({ page: 0, size: 100 },
|
||||
(resp) => {
|
||||
const bandi = resp?.data?.body || [];
|
||||
// per ogni bando, tento di caricare lo schema di rendicontazione
|
||||
const baseRows = bandi.map(b => ({ bando: b, schema: null, schemaLoaded: false }));
|
||||
setRows(baseRows);
|
||||
setLoading(false);
|
||||
|
||||
// Caricamento schemi in parallelo — update progressivo
|
||||
bandi.forEach((b, idx) => {
|
||||
RendicontazioneService.getSchemaByCallId(b.id,
|
||||
(schemaResp) => {
|
||||
setRows(prev => prev.map((r, i) => i === idx
|
||||
? { ...r, schema: schemaResp?.data || null, schemaLoaded: true }
|
||||
: r));
|
||||
},
|
||||
(err) => {
|
||||
// 404 = schema non ancora creato, tutto ok
|
||||
setRows(prev => prev.map((r, i) => i === idx
|
||||
? { ...r, schema: null, schemaLoaded: true }
|
||||
: r));
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
(err) => {
|
||||
setLoading(false);
|
||||
if (toast.current) {
|
||||
toast.current.show({ severity: 'error', summary: __('Errore', 'gepafin'), detail: err?.message || __('Impossibile caricare i bandi', 'gepafin') });
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const editSchema = (bandoId) => {
|
||||
navigate(`/bandi/${bandoId}/rendicontazione-schema`);
|
||||
};
|
||||
|
||||
// --- column templates ---
|
||||
const bandoNameTpl = (row) => (
|
||||
<div>
|
||||
<strong>{row.bando.name || `Bando #${row.bando.id}`}</strong>
|
||||
{row.bando.descriptionShort && (
|
||||
<div><small className="text-color-secondary">{row.bando.descriptionShort.slice(0, 80)}{row.bando.descriptionShort.length > 80 ? '…' : ''}</small></div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const bandoStatusTpl = (row) => (
|
||||
<Tag value={row.bando.status || '—'} severity={row.bando.status === 'PUBLISH' ? 'success' : 'secondary'} />
|
||||
);
|
||||
|
||||
const schemaStatusTpl = (row) => {
|
||||
if (!row.schemaLoaded) return <Skeleton width="6rem" height="1.5rem" />;
|
||||
const key = row.schema ? row.schema.status : null;
|
||||
const conf = SCHEMA_STATUS_CONFIG[key] || SCHEMA_STATUS_CONFIG[null];
|
||||
return <Tag icon={conf.icon} value={conf.label} severity={conf.severity} />;
|
||||
};
|
||||
|
||||
const actionsTpl = (row) => {
|
||||
if (!row.schemaLoaded) return <Skeleton width="8rem" height="2rem" />;
|
||||
const hasSchema = !!row.schema;
|
||||
return (
|
||||
<Button
|
||||
icon={hasSchema ? 'pi pi-pencil' : 'pi pi-plus-circle'}
|
||||
label={hasSchema ? __('Modifica', 'gepafin') : __('Crea schema', 'gepafin')}
|
||||
className={hasSchema ? 'p-button-outlined p-button-sm' : 'p-button-sm'}
|
||||
severity={hasSchema ? null : 'success'}
|
||||
onClick={() => editSchema(row.bando.id)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="page-wrapper">
|
||||
<Toast ref={toast} />
|
||||
|
||||
<div className="mb-3">
|
||||
<h2 className="mb-1">{__('Gestione rendicontazione', 'gepafin')}</h2>
|
||||
<p className="m-0 text-color-secondary">
|
||||
{__('Configura per ciascun bando lo schema di rendicontazione che i beneficiari vedranno dopo la firma del contratto. Ogni bando ha uno schema: categorie di spesa, regole ULA, documenti richiesti.', 'gepafin')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<DataTable
|
||||
value={rows}
|
||||
loading={loading}
|
||||
dataKey="bando.id"
|
||||
emptyMessage={__('Nessun bando disponibile', 'gepafin')}
|
||||
paginator={rows.length > 15}
|
||||
rows={15}
|
||||
stripedRows
|
||||
>
|
||||
<Column field="bando.id" header="ID" style={{ width: '60px' }} />
|
||||
<Column field="bando.name" header={__('Bando', 'gepafin')} body={bandoNameTpl} />
|
||||
<Column field="bando.status" header={__('Stato bando', 'gepafin')} body={bandoStatusTpl} style={{ width: '140px' }} />
|
||||
<Column header={__('Schema rendicontazione', 'gepafin')} body={schemaStatusTpl} style={{ width: '180px' }} />
|
||||
<Column header={__('Azioni', 'gepafin')} body={actionsTpl} style={{ width: '180px' }} />
|
||||
</DataTable>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RendicontazioneHome;
|
||||
Reference in New Issue
Block a user