import { useEffect, useState } from 'react'; import type { FormEvent } from 'react'; import { Link, useSearchParams } from 'react-router'; import { Alert, EmptyState, LoadingBlock, errorMessage } from '../components/Feedback'; import { Icon } from '../components/Icon'; import { useOperationalContext } from '../context/OperationalContext'; import { DocumentCenterTabs } from '../features/documents/DocumentCenterTabs'; import { OperationalFilters } from '../features/inspections/OperationalFilters'; import { listInspectionReportsF4, listPendingInspectionReportsF4, } from '../lib/reportWorkflowApi'; import type { InspectionReportDetailF4, PageMetaF4, PendingInspectionReportF4, } from '../lib/reportWorkflowApi'; import { formatDate } from '../lib/format'; function contextLabel(items: Array<{ name: string }>, empty: string): string { if (items.length === 0) return empty; const first = items[0]; if (!first) return empty; if (items.length === 1) return first.name; return `${first.name} +${items.length - 1}`; } function statusLabel(report: InspectionReportDetailF4): string { if (report.status === 'OFFICIALIZED') return report.gedoIfIdentifier ? `GEDO · ${report.gedoIfIdentifier}` : 'Oficializado en GEDO'; if (report.status === 'WORKING') return 'En preparación'; if (report.status === 'CANCELLED') return 'Cancelado'; return 'Legado congelado'; } function statusClass(report: InspectionReportDetailF4): string { if (report.status === 'OFFICIALIZED') return 'active'; if (report.status === 'CANCELLED') return 'danger'; return 'pending'; } export function ReportsPage() { const [params, setParams] = useSearchParams(); const operationalContext = useOperationalContext(); const view = params.get('view') === 'pending' ? 'pending' : 'issued'; const search = params.get('search') ?? ''; const year = params.get('year') ?? ''; const companyId = operationalContext.companyId || params.get('companyId') || ''; const areaId = operationalContext.areaId || params.get('areaId') || ''; const inspectorId = params.get('inspectorId') ?? ''; const dateFrom = params.get('dateFrom') ?? ''; const dateTo = params.get('dateTo') ?? ''; const page = Math.max(1, Number(params.get('page') ?? 1) || 1); const [draftSearch, setDraftSearch] = useState(search); const [issued, setIssued] = useState([]); const [pending, setPending] = useState([]); const [meta, setMeta] = useState({ page: 1, pageSize: 25, total: 0, totalPages: 0 }); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); useEffect(() => { let cancelled = false; setLoading(true); setError(''); const query = { page, pageSize: 25, search, year: year ? Number(year) : undefined, companyId, areaId, inspectorId, dateFrom, dateTo, }; const load = async () => { try { if (view === 'issued') { const response = await listInspectionReportsF4(query); if (cancelled) return; setIssued(response.data); setPending([]); setMeta(response.meta); } else { const response = await listPendingInspectionReportsF4(query); if (cancelled) return; setPending(response.data); setIssued([]); setMeta(response.meta); } } catch (requestError) { if (!cancelled) setError(errorMessage(requestError)); } finally { if (!cancelled) setLoading(false); } }; void load(); return () => { cancelled = true; }; }, [page, search, view, year, companyId, areaId, inspectorId, dateFrom, dateTo]); const setFilter = (key: string, value: string) => { const next = new URLSearchParams(params); if (key === 'areaId') { operationalContext.setAreaId(value); next.delete('areaId'); next.delete('companyId'); } else if (key === 'companyId') { operationalContext.setCompanyId(value); next.delete('companyId'); } else { value ? next.set(key, value) : next.delete(key); } next.delete('page'); setParams(next); }; const applySearch = (event: FormEvent) => { event.preventDefault(); setFilter('search', draftSearch.trim()); }; const setPage = (value: number) => { const next = new URLSearchParams(params); value > 1 ? next.set('page', String(value)) : next.delete('page'); setParams(next); }; const empty = view === 'issued' ? issued.length === 0 : pending.length === 0; return
CENTRO DOCUMENTAL

Informes

Cada Acta sellada genera un único INF editable. Luego el IF y PDF oficial de GEDO completan su trazabilidad institucional.

{view === 'pending' &&

Actas listas para generar INF. Esta bandeja sólo muestra Actas selladas que todavía no tienen su Informe 1 a 1.

} {error && {error}} {loading ? : empty ? :
{meta.total} {view === 'issued' ? `informe${meta.total === 1 ? '' : 's'}` : `acta${meta.total === 1 ? '' : 's'} pendiente${meta.total === 1 ? '' : 's'}`}Página {page} de {Math.max(meta.totalPages, 1)}
{view === 'issued' ? : }{view === 'issued' ? issued.map((report) => ) : pending.map((item) => )}
InformeEmpresa / áreaActaHallazgosWordEstado
Acta selladaEmpresa / áreaInspecciónHallazgosSellado
{report.code}{report.title} · {formatDate(report.generatedAt)}
{contextLabel(report.companies, 'Empresa sin asignar')}{contextLabel(report.areas, 'Área sin asignar')}
{report.act.code}{report.act.title} {report.findingCount} {report.wordStatus === 'READY' ? 'Disponible' : report.wordStatus === 'FAILED' ? 'Error' : 'Pendiente'} {statusLabel(report)}
{item.actCode}{item.actTitle} · {formatDate(item.occurredAt)}
{contextLabel(item.companies, 'Empresa sin asignar')}{contextLabel(item.areas, 'Área sin asignar')}
{item.visitCode} {item.findingCount} {formatDate(item.sealedAt)}
{meta.total ? `${(page - 1) * meta.pageSize + 1}–${Math.min(page * meta.pageSize, meta.total)} de ${meta.total}` : '0 resultados'}
}
; }