- 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
146 lines
5.9 KiB
JavaScript
146 lines
5.9 KiB
JavaScript
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;
|