feat(rendicontazione): lato beneficiario - lista pratiche + compilazione + submit
- 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
This commit is contained in:
133
src/modules/rendicontazione/pages/RendicontazioniMie.js
Normal file
133
src/modules/rendicontazione/pages/RendicontazioniMie.js
Normal file
@@ -0,0 +1,133 @@
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { Button } from 'primereact/button';
|
||||
import { DataTable } from 'primereact/datatable';
|
||||
import { Column } from 'primereact/column';
|
||||
import { Tag } from 'primereact/tag';
|
||||
import { Toast } from 'primereact/toast';
|
||||
import { Skeleton } from 'primereact/skeleton';
|
||||
|
||||
import RendicontazioneService from '../service/rendicontazioneService';
|
||||
|
||||
const STATUS_TAGS = {
|
||||
NOT_STARTED: { severity: 'info', label: 'Da avviare' },
|
||||
DRAFT: { severity: 'warning', label: 'In compilazione' },
|
||||
SUBMITTED: { severity: 'info', label: 'Inviata' },
|
||||
UNDER_REVIEW: { severity: 'info', label: 'In valutazione' },
|
||||
APPROVED: { severity: 'success', label: 'Approvata' },
|
||||
REJECTED: { severity: 'danger', label: 'Respinta' },
|
||||
AWAITING_AMENDMENT: { severity: 'warning', label: 'Soccorso istruttorio' }
|
||||
};
|
||||
|
||||
const RendicontazioniMie = () => {
|
||||
const navigate = useNavigate();
|
||||
const toast = useRef(null);
|
||||
const [rows, setRows] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const load = () => {
|
||||
setLoading(true);
|
||||
RendicontazioneService.listMine(
|
||||
(resp) => {
|
||||
const practices = (resp?.data?.practices || []).map(p => ({ ...p, isReady: false }));
|
||||
const ready = (resp?.data?.ready_to_start || []).map(r => ({ ...r, isReady: true }));
|
||||
setRows([...practices, ...ready]);
|
||||
setLoading(false);
|
||||
},
|
||||
(err) => {
|
||||
toast.current?.show({ severity: 'error', summary: __('Errore', 'gepafin'), detail: err?.detail });
|
||||
setLoading(false);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => { load(); /* eslint-disable-next-line */ }, []);
|
||||
|
||||
const handleStart = (applicationId) => {
|
||||
RendicontazioneService.startPractice(applicationId,
|
||||
(resp) => {
|
||||
toast.current?.show({ severity: 'success', summary: __('Rendicontazione avviata', 'gepafin') });
|
||||
navigate(`/rendicontazioni/${resp.data.id}`);
|
||||
},
|
||||
(err) => toast.current?.show({ severity: 'error', summary: __('Avvio fallito', 'gepafin'), detail: err?.detail })
|
||||
);
|
||||
};
|
||||
|
||||
const callTpl = (row) => (
|
||||
<div>
|
||||
<strong>{row.call_name || `Bando #${row.call_id}`}</strong>
|
||||
<div><small className="text-color-secondary">{row.company_name}</small></div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const erogatoTpl = (row) => {
|
||||
const v = Number(row.amount_erogato || 0);
|
||||
return <strong>€ {v.toLocaleString('it-IT', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</strong>;
|
||||
};
|
||||
|
||||
const statusTpl = (row) => {
|
||||
const key = row.isReady ? 'NOT_STARTED' : (row.status || 'DRAFT');
|
||||
const conf = STATUS_TAGS[key] || { severity: 'secondary', label: key };
|
||||
return <Tag value={conf.label} severity={conf.severity} />;
|
||||
};
|
||||
|
||||
const progressTpl = (row) => {
|
||||
if (row.isReady) return <span className="text-color-secondary">—</span>;
|
||||
return (
|
||||
<span className="text-color-secondary" style={{ fontSize: '0.9em' }}>
|
||||
{row.invoice_count || 0} {__('fatture','gepafin')} · {row.ula_count || 0} {__('dipendenti','gepafin')} · {row.document_count || 0} {__('doc','gepafin')}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const actionsTpl = (row) => {
|
||||
if (row.isReady) {
|
||||
return <Button icon="pi pi-play" label={__('Avvia rendicontazione', 'gepafin')}
|
||||
size="small" severity="success" onClick={() => handleStart(row.application_id)} />;
|
||||
}
|
||||
const isEditable = row.status === 'DRAFT';
|
||||
return <Button icon={isEditable ? 'pi pi-pencil' : 'pi pi-eye'}
|
||||
label={isEditable ? __('Continua', 'gepafin') : __('Apri', 'gepafin')}
|
||||
size="small" outlined={!isEditable}
|
||||
onClick={() => navigate(`/rendicontazioni/${row.id}`)} />;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="appPage">
|
||||
<Toast ref={toast} />
|
||||
|
||||
<div className="appPage__pageHeader">
|
||||
<h1>{__('Le mie rendicontazioni', 'gepafin')}</h1>
|
||||
<p>{__('Per ogni pratica finanziata puoi avviare la rendicontazione delle spese e il calcolo della remissione del debito.', 'gepafin')}</p>
|
||||
</div>
|
||||
|
||||
<div className="appPage__spacer"></div>
|
||||
|
||||
<div className="appPageSection">
|
||||
{loading && <Skeleton width="100%" height="10rem" />}
|
||||
{!loading && rows.length === 0 && (
|
||||
<div style={{ padding: '2rem', textAlign: 'center', width: '100%' }}>
|
||||
<i className="pi pi-inbox" style={{ fontSize: '2.5rem', color: 'var(--text-color-secondary)', display: 'block', marginBottom: '0.75rem' }} />
|
||||
<p>{__('Non ci sono rendicontazioni da avviare al momento.', 'gepafin')}</p>
|
||||
<small className="text-color-secondary">
|
||||
{__('Le rendicontazioni diventano disponibili dopo la firma del contratto e quando l\'ente ha pubblicato lo schema di rendicontazione per il bando.', 'gepafin')}
|
||||
</small>
|
||||
</div>
|
||||
)}
|
||||
{!loading && rows.length > 0 && (
|
||||
<DataTable value={rows} dataKey="id" stripedRows responsiveLayout="scroll" style={{ width: '100%' }}>
|
||||
<Column header={__('Bando', 'gepafin')} body={callTpl} />
|
||||
<Column header={__('Importo erogato', 'gepafin')} body={erogatoTpl} style={{ width: '180px' }} />
|
||||
<Column header={__('Stato', 'gepafin')} body={statusTpl} style={{ width: '180px' }} />
|
||||
<Column header={__('Avanzamento', 'gepafin')} body={progressTpl} />
|
||||
<Column header={__('Azione', 'gepafin')} body={actionsTpl} style={{ width: '220px' }} />
|
||||
</DataTable>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RendicontazioniMie;
|
||||
Reference in New Issue
Block a user