diff --git a/src/modules/rendicontazione/components/CompanyDocumentPicker.js b/src/modules/rendicontazione/components/CompanyDocumentPicker.js index 3382aa0..a94bf84 100644 --- a/src/modules/rendicontazione/components/CompanyDocumentPicker.js +++ b/src/modules/rendicontazione/components/CompanyDocumentPicker.js @@ -4,11 +4,11 @@ import { Dialog } from 'primereact/dialog'; import { DataTable } from 'primereact/datatable'; import { Column } from 'primereact/column'; import { Button } from 'primereact/button'; -import { RadioButton } from 'primereact/radiobutton'; import { Tag } from 'primereact/tag'; import { InputText } from 'primereact/inputtext'; import { Dropdown } from 'primereact/dropdown'; import { Skeleton } from 'primereact/skeleton'; +import { Message } from 'primereact/message'; import CompanyDocumentsService from '../../../service/company-documents-service'; @@ -16,20 +16,22 @@ import CompanyDocumentsService from '../../../service/company-documents-service' * Modal per selezionare un documento dal repository della company. * * Mostra tutti i company_document della company (BE Gepafin /companyDocument/company/{id}), - * permette filtri per tipo, stato e testo libero, selezione radio, conferma. - * Alla conferma chiama onSelect(companyDocument). + * filtri per categoria/stato/ricerca, selezione single-row via DataTable, semaforo stato. * * Props: - * visible boolean - * companyId number - * onHide () => void - * onSelect (doc) => void — doc: {id, fileName, type, status, expirationDate, ...} - * currentSourceId number | null — id gia selezionato, per evidenziarlo + * visible boolean + * companyId number + * onHide () => void + * onSelect (doc) => void — doc: {id, fileName, status, expirationDate, category, ...} + * currentSourceId number | null — id gia selezionato, per evidenziarlo + * + * Nota: il BE Spring filtra fuori i doc EXPIRED — non compariranno qui. + * Il gate submit comunque blocca via JOIN live lato microservizio. */ const STATUS_CFG = { - VALID: { severity: 'success', label: __('Valido', 'gepafin'), icon: 'pi pi-check-circle' }, - DUE: { severity: 'warning', label: __('In scadenza', 'gepafin'), icon: 'pi pi-exclamation-triangle' }, - EXPIRED: { severity: 'danger', label: __('Scaduto', 'gepafin'), icon: 'pi pi-times-circle' }, + VALID: { severity: 'success', label: 'Valido', icon: 'pi pi-check-circle', color: 'var(--green-500)' }, + DUE: { severity: 'warning', label: 'In scadenza', icon: 'pi pi-exclamation-triangle', color: 'var(--yellow-500)' }, + EXPIRED: { severity: 'danger', label: 'Scaduto', icon: 'pi pi-times-circle', color: 'var(--red-500)' }, }; const formatDate = (d) => { @@ -40,6 +42,17 @@ const formatDate = (d) => { } catch { return String(d); } }; +const daysUntil = (d) => { + if (!d) return null; + try { + const dt = typeof d === 'string' ? new Date(d) : d; + const ms = dt.getTime() - Date.now(); + return Math.ceil(ms / (1000 * 60 * 60 * 24)); + } catch { return null; } +}; + +const getCategoryName = (d) => (d.category && d.category.categoryName) || d.type || '—'; + const CompanyDocumentPicker = ({ visible, companyId, onHide, onSelect, currentSourceId = null }) => { const [loading, setLoading] = useState(false); const [docs, setDocs] = useState([]); @@ -47,10 +60,9 @@ const CompanyDocumentPicker = ({ visible, companyId, onHide, onSelect, currentSo const [selectedId, setSelectedId] = useState(null); const [search, setSearch] = useState(''); - const [typeFilter, setTypeFilter] = useState(null); + const [categoryFilter, setCategoryFilter] = useState(null); const [statusFilter, setStatusFilter] = useState(null); - // load al mount useEffect(() => { if (!visible || !companyId) return; setLoading(true); @@ -64,63 +76,75 @@ const CompanyDocumentPicker = ({ visible, companyId, onHide, onSelect, currentSo setLoading(false); }, () => { - setError(__('Impossibile caricare i documenti del repository', 'gepafin')); + setError(__('Impossibile caricare i documenti del repository. Riprova tra qualche istante.', 'gepafin')); setLoading(false); } ); - // reset filtri all'apertura - setSearch(''); setTypeFilter(null); setStatusFilter(null); + setSearch(''); setCategoryFilter(null); setStatusFilter(null); }, [visible, companyId, currentSourceId]); - // opzioni filtri derivate dai dati - const typeOptions = useMemo(() => { - const uniq = Array.from(new Set(docs.map(d => d.type).filter(Boolean))).sort(); + const categoryOptions = useMemo(() => { + const uniq = Array.from(new Set(docs.map(getCategoryName).filter(Boolean))).sort(); return uniq.map(t => ({ label: t, value: t })); }, [docs]); const statusOptions = [ - { label: __('Tutti', 'gepafin'), value: null }, - { label: __('Validi', 'gepafin'), value: 'VALID' }, - { label: __('In scadenza', 'gepafin'), value: 'DUE' }, - { label: __('Scaduti', 'gepafin'), value: 'EXPIRED' }, + { label: __('Validi', 'gepafin'), value: 'VALID' }, + { label: __('In scadenza', 'gepafin'), value: 'DUE' }, ]; const filteredDocs = useMemo(() => { const s = (search || '').trim().toLowerCase(); return docs.filter(d => { - if (typeFilter && d.type !== typeFilter) return false; + if (categoryFilter && getCategoryName(d) !== categoryFilter) return false; if (statusFilter && d.status !== statusFilter) return false; if (s) { - const hay = [d.fileName, d.name, d.type].filter(Boolean).join(' ').toLowerCase(); + const hay = [d.fileName, d.name, getCategoryName(d)].filter(Boolean).join(' ').toLowerCase(); if (!hay.includes(s)) return false; } return true; }); - }, [docs, search, typeFilter, statusFilter]); + }, [docs, search, categoryFilter, statusFilter]); // templates colonne - const selectionTpl = (row) => ( - setSelectedId(row.id)} /> - ); - const nameTpl = (row) => (
- {row.fileName || row.name || `Doc #${row.id}`} +
+ + {row.fileName || row.name || `Doc #${row.id}`} +
{row.name && row.name !== row.fileName && ( -
{row.name}
+
+ {row.name} +
)}
); - const typeTpl = (row) => {row.type || '—'}; + const categoryTpl = (row) => ( + + ); const statusTpl = (row) => { const cfg = STATUS_CFG[row.status] || { severity: 'secondary', label: row.status || '—', icon: 'pi pi-question' }; - return ; + const days = daysUntil(row.expirationDate); + return ( +
+ + {row.status === 'DUE' && days != null && days >= 0 && ( +
scade tra {days} {days === 1 ? 'giorno' : 'giorni'}
+ )} +
+ ); }; - const expiryTpl = (row) => formatDate(row.expirationDate); + const expiryTpl = (row) => ( +
+ {formatDate(row.expirationDate)} +
+ ); const chosenDoc = useMemo( () => docs.find(d => d.id === selectedId) || null, @@ -128,17 +152,21 @@ const CompanyDocumentPicker = ({ visible, companyId, onHide, onSelect, currentSo ); const footer = ( -
-
- {chosenDoc && ( - - {__('Selezionato', 'gepafin')}: {chosenDoc.fileName || chosenDoc.name} - {' '} - +
+
+ {chosenDoc ? ( +
+ {__('Selezionato', 'gepafin')}: + {chosenDoc.fileName || chosenDoc.name} + +
+ ) : ( + {__('Seleziona una riga per continuare', 'gepafin')} )}
-
+