diff --git a/web/src/pages/admin/hse/IncidentesPage.tsx b/web/src/pages/admin/hse/IncidentesPage.tsx index 44b1fc7..28e3ab1 100644 --- a/web/src/pages/admin/hse/IncidentesPage.tsx +++ b/web/src/pages/admin/hse/IncidentesPage.tsx @@ -1,45 +1,739 @@ -/** - * IncidentesPage Component - * Gestion de incidentes HSE - */ +import { useState } from 'react'; +import { Link } from 'react-router-dom'; +import { + Plus, + Pencil, + Eye, + Search, + FileText, + XCircle, + AlertTriangle, + Activity, +} from 'lucide-react'; +import { + useIncidentes, + useCreateIncidente, + useUpdateIncidente, + useInvestigateIncidente, + useCloseIncidente, +} from '../../../hooks/useHSE'; +import { useFraccionamientos } from '../../../hooks/useConstruccion'; +import { Fraccionamiento } from '../../../services/construccion/fraccionamientos.api'; +import { + Incidente, + TipoIncidente, + GravedadIncidente, + EstadoIncidente, + CreateIncidenteDto, +} from '../../../services/hse/incidentes.api'; +import clsx from 'clsx'; -import { AlertTriangle } from 'lucide-react'; +const tipoColors: Record = { + accidente: 'bg-red-100 text-red-800', + incidente: 'bg-yellow-100 text-yellow-800', + casi_accidente: 'bg-blue-100 text-blue-800', +}; + +const tipoLabels: Record = { + accidente: 'Accidente', + incidente: 'Incidente', + casi_accidente: 'Casi Accidente', +}; + +const gravedadColors: Record = { + leve: 'bg-green-100 text-green-800', + moderado: 'bg-yellow-100 text-yellow-800', + grave: 'bg-orange-100 text-orange-800', + fatal: 'bg-red-100 text-red-800', +}; + +const gravedadLabels: Record = { + leve: 'Leve', + moderado: 'Moderado', + grave: 'Grave', + fatal: 'Fatal', +}; + +const estadoColors: Record = { + abierto: 'bg-yellow-100 text-yellow-800', + en_investigacion: 'bg-blue-100 text-blue-800', + cerrado: 'bg-green-100 text-green-800', +}; + +const estadoLabels: Record = { + abierto: 'Abierto', + en_investigacion: 'En Investigación', + cerrado: 'Cerrado', +}; export function IncidentesPage() { + const [search, setSearch] = useState(''); + const [tipoFilter, setTipoFilter] = useState(''); + const [gravedadFilter, setGravedadFilter] = useState(''); + const [estadoFilter, setEstadoFilter] = useState(''); + const [fraccionamientoFilter, setFraccionamientoFilter] = useState(''); + const [dateFrom, setDateFrom] = useState(''); + const [dateTo, setDateTo] = useState(''); + const [showModal, setShowModal] = useState(false); + const [editingItem, setEditingItem] = useState(null); + const [investigateModal, setInvestigateModal] = useState(null); + const [closeModal, setCloseModal] = useState(null); + + const { data, isLoading, error } = useIncidentes({ + search: search || undefined, + tipo: tipoFilter || undefined, + gravedad: gravedadFilter || undefined, + estado: estadoFilter || undefined, + fraccionamientoId: fraccionamientoFilter || undefined, + dateFrom: dateFrom || undefined, + dateTo: dateTo || undefined, + }); + + const { data: fraccionamientosData } = useFraccionamientos(); + + const createMutation = useCreateIncidente(); + const updateMutation = useUpdateIncidente(); + const investigateMutation = useInvestigateIncidente(); + const closeMutation = useCloseIncidente(); + + const handleSubmit = async (formData: CreateIncidenteDto) => { + if (editingItem) { + await updateMutation.mutateAsync({ id: editingItem.id, data: formData }); + } else { + await createMutation.mutateAsync(formData); + } + setShowModal(false); + setEditingItem(null); + }; + + const handleInvestigate = async (id: string, investigadorId: string) => { + await investigateMutation.mutateAsync({ + id, + investigadorId, + fechaInvestigacion: new Date().toISOString(), + }); + setInvestigateModal(null); + }; + + const handleClose = async (id: string, observaciones?: string) => { + await closeMutation.mutateAsync({ + id, + fechaCierre: new Date().toISOString(), + observaciones, + }); + setCloseModal(null); + }; + + const incidentes = data?.items || []; + const fraccionamientos = fraccionamientosData?.items || []; + + // Generate folio for display (assuming incremental numbering) + const generateFolio = (index: number) => { + const year = new Date().getFullYear(); + return `INC-${year}-${String(index + 1).padStart(4, '0')}`; + }; + return (

Incidentes HSE

-

- Gestion de incidentes de seguridad, salud y medio ambiente -

+

Gestión de incidentes de seguridad, salud y medio ambiente

+
+ +
+ + {/* Filters */} +
+
+
+
+ + setSearch(e.target.value)} + /> +
+ +
+
+ + + + setDateFrom(e.target.value)} + placeholder="Desde" + /> + setDateTo(e.target.value)} + placeholder="Hasta" + /> +
-
-
-
- + {/* Table */} +
+ {isLoading ? ( +
Cargando...
+ ) : error ? ( +
Error al cargar los datos
+ ) : incidentes.length === 0 ? ( +
+ No hay incidentes registrados
-

- Modulo de Incidentes en Desarrollo -

-

- Este modulo permitira registrar, clasificar y dar seguimiento a incidentes - de seguridad, salud ocupacional y medio ambiente. -

-
-

Funcionalidades proximas:

-
    -
  • - Registro de incidentes y accidentes
  • -
  • - Clasificacion por severidad y tipo
  • -
  • - Seguimiento de investigaciones
  • -
  • - Planes de accion correctiva
  • -
  • - Reportes y estadisticas
  • -
+ ) : ( + + + + + + + + + + + + + + {incidentes.map((item, index) => ( + + + + + + + + + + ))} + +
+ Folio + + Fecha/Hora + + Tipo + + Gravedad + + Descripción + + Estado + + Acciones +
+ {generateFolio(index)} + +
+ {new Date(item.fecha).toLocaleDateString()} + {item.hora && ( +
{item.hora}
+ )} +
+
+ + {tipoLabels[item.tipo]} + + + + {gravedadLabels[item.gravedad]} + + + {item.descripcion} + + + {estadoLabels[item.estado]} + + +
+ + + + + {item.estado === 'abierto' && ( + + )} + {item.estado === 'en_investigacion' && ( + + )} +
+
+ )} +
+ + {/* Create/Edit Modal */} + {showModal && ( + { + setShowModal(false); + setEditingItem(null); + }} + onSubmit={handleSubmit} + isLoading={createMutation.isPending || updateMutation.isPending} + /> + )} + + {/* Investigate Modal */} + {investigateModal && ( + setInvestigateModal(null)} + onSubmit={(investigadorId) => handleInvestigate(investigateModal.id, investigadorId)} + isLoading={investigateMutation.isPending} + /> + )} + + {/* Close Modal */} + {closeModal && ( + setCloseModal(null)} + onSubmit={(observaciones) => handleClose(closeModal.id, observaciones)} + isLoading={closeMutation.isPending} + /> + )} +
+ ); +} + +// Modal Component +interface IncidenteModalProps { + item: Incidente | null; + fraccionamientos: Fraccionamiento[]; + onClose: () => void; + onSubmit: (data: CreateIncidenteDto) => Promise; + isLoading: boolean; +} + +function IncidenteModal({ + item, + fraccionamientos, + onClose, + onSubmit, + isLoading, +}: IncidenteModalProps) { + const [formData, setFormData] = useState({ + fraccionamientoId: item?.fraccionamientoId || '', + tipo: item?.tipo || 'incidente', + gravedad: item?.gravedad || 'leve', + fecha: item?.fecha?.split('T')[0] || new Date().toISOString().split('T')[0], + hora: item?.hora || '', + ubicacion: item?.ubicacion || '', + descripcion: item?.descripcion || '', + causas: item?.causas || '', + acciones: item?.acciones || '', + observaciones: item?.observaciones || '', + }); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + await onSubmit(formData); + }; + + return ( +
+
+

+ {item ? 'Editar Incidente' : 'Registrar Nuevo Incidente'} +

+
+
+
+ + setFormData({ ...formData, fecha: e.target.value })} + /> +
+
+ + setFormData({ ...formData, hora: e.target.value })} + /> +
-
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+ + setFormData({ ...formData, ubicacion: e.target.value })} + /> +
+ +
+ +