fix(HistoriaClinica): agregar handlers de otrosLaboratorios en store y corregir ReferenceError
This commit is contained in:
@@ -7,7 +7,7 @@ import type {
|
||||
Grupo,
|
||||
Internacion,
|
||||
Evolucion,
|
||||
Laboratorio,
|
||||
Laboratorio, OtroLaboratorio,
|
||||
Glucemia,
|
||||
AcidoBase,
|
||||
Cultivo,
|
||||
@@ -41,6 +41,7 @@ interface HospitalState {
|
||||
internaciones: Internacion[];
|
||||
evoluciones: Evolucion[];
|
||||
laboratorios: Laboratorio[];
|
||||
otrosLaboratorios: OtroLaboratorio[];
|
||||
glucemias: Glucemia[];
|
||||
acidosBase: AcidoBase[];
|
||||
cultivos: Cultivo[];
|
||||
@@ -66,6 +67,7 @@ const defaultState = (): HospitalState => ({
|
||||
internaciones: [],
|
||||
evoluciones: [],
|
||||
laboratorios: [],
|
||||
otrosLaboratorios: [],
|
||||
glucemias: [],
|
||||
acidosBase: [],
|
||||
cultivos: [],
|
||||
@@ -744,6 +746,51 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
||||
}
|
||||
}, [state, checkGrupoPermission, apiCall]);
|
||||
|
||||
// Acciones de otros laboratorios
|
||||
const agregarOtroLaboratorio = useCallback(async (otroLaboratorio: Omit<OtroLaboratorio, 'id'>) => {
|
||||
const nuevo: OtroLaboratorio = {
|
||||
...otroLaboratorio,
|
||||
id: generateUUID(),
|
||||
};
|
||||
try {
|
||||
await apiCall('POST', '/api/otros-laboratorios', nuevo);
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
otrosLaboratorios: [...prev.otrosLaboratorios, nuevo],
|
||||
}));
|
||||
return nuevo.id;
|
||||
} catch (err) {
|
||||
console.error('Error al agregar otro laboratorio:', err);
|
||||
throw err;
|
||||
}
|
||||
}, [apiCall]);
|
||||
|
||||
const eliminarOtroLaboratorio = useCallback(async (id: string) => {
|
||||
try {
|
||||
await apiCall('DELETE', `/api/otros-laboratorios/${id}`);
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
otrosLaboratorios: prev.otrosLaboratorios.filter(o => o.id !== id),
|
||||
}));
|
||||
} catch (err) {
|
||||
console.error('Error al eliminar otro laboratorio:', err);
|
||||
throw err;
|
||||
}
|
||||
}, [apiCall]);
|
||||
|
||||
const actualizarOtroLaboratorio = useCallback(async (id: string, datos: Partial<OtroLaboratorio>) => {
|
||||
try {
|
||||
await apiCall('PUT', `/api/otros-laboratorios/${id}`, datos);
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
otrosLaboratorios: prev.otrosLaboratorios.map(o => o.id === id ? { ...o, ...datos } : o),
|
||||
}));
|
||||
} catch (err) {
|
||||
console.error('Error al actualizar otro laboratorio:', err);
|
||||
throw err;
|
||||
}
|
||||
}, [apiCall]);
|
||||
|
||||
// Acciones de glucemias
|
||||
const agregarGlucemia = useCallback(async (glucemia: Omit<Glucemia, 'id'>) => {
|
||||
const internacion = state.internaciones.find(i => i.id === glucemia.internacionId);
|
||||
@@ -1522,6 +1569,9 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
||||
agregarLaboratorio,
|
||||
actualizarLaboratorio,
|
||||
eliminarLaboratorio,
|
||||
agregarOtroLaboratorio,
|
||||
actualizarOtroLaboratorio,
|
||||
eliminarOtroLaboratorio,
|
||||
agregarGlucemia,
|
||||
actualizarGlucemia,
|
||||
eliminarGlucemia,
|
||||
|
||||
@@ -60,7 +60,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@
|
||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
|
||||
import type { Laboratorio, Glucemia, AcidoBase, Cultivo, Paciente, ResultadoLaboratorio, Internacion, Evolucion, Cama, EstudioComplementario, Interconsulta, ATB, Indicacion, MovimientoIndicacion, Pendiente } from '@/types';
|
||||
import type { Laboratorio, Glucemia, AcidoBase, Cultivo, Paciente, ResultadoLaboratorio, Internacion, Evolucion, Cama, EstudioComplementario, Interconsulta, ATB, Indicacion, MovimientoIndicacion, Pendiente, OtroLaboratorio } from '@/types';
|
||||
import { formatDateDDMMYYYY, getLocalToday } from '@/lib/utils';
|
||||
import { SeccionIndicaciones } from './SeccionIndicaciones';
|
||||
|
||||
@@ -251,7 +251,7 @@ export function HistoriaClinica({
|
||||
}: HistoriaClinicaProps) {
|
||||
const [tabActivo, setTabActivo] = useState('evoluciones');
|
||||
const [portalNode, setPortalNode] = useState<HTMLDivElement | null>(null);
|
||||
const { pendientes } = useHospitalStore();
|
||||
const { pendientes, otrosLaboratorios, agregarOtroLaboratorio } = useHospitalStore();
|
||||
const indicacionesActivas = (indicadores || []).filter(i => i.estado === 'Activa');
|
||||
const pendientesActivos = (pendientes || []).filter(p => p.pacienteId === paciente.id && p.estado === 'pendiente');
|
||||
|
||||
@@ -568,7 +568,19 @@ export function HistoriaClinica({
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="laboratorios" className="mt-4 min-w-0 w-full overflow-hidden">
|
||||
<SeccionLaboratorios portalNode={portalNode} lab={laboratorios} patientId={paciente.id} internacionId={internacion.id} add={onAgregarLaboratorio} update={onActualizarLaboratorio} del={onEliminarLaboratorio} addAcidoBase={onAgregarAcidoBase} canEdit={canEdit} />
|
||||
<SeccionLaboratorios
|
||||
portalNode={portalNode}
|
||||
lab={laboratorios}
|
||||
otrosLaboratorios={otrosLaboratorios}
|
||||
patientId={paciente.id}
|
||||
internacionId={internacion.id}
|
||||
add={onAgregarLaboratorio}
|
||||
update={onActualizarLaboratorio}
|
||||
del={onEliminarLaboratorio}
|
||||
addAcidoBase={onAgregarAcidoBase}
|
||||
addOtroLaboratorio={agregarOtroLaboratorio}
|
||||
canEdit={canEdit}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="evoluciones" className="mt-4">
|
||||
@@ -925,20 +937,26 @@ function SeccionGlucemias({ glucemias, patientId, internacionId, add, update, de
|
||||
);
|
||||
}
|
||||
|
||||
function SeccionLaboratorios({ lab, patientId, internacionId, add, update, del, addAcidoBase, canEdit, portalNode}: {
|
||||
function SeccionLaboratorios({ lab, otrosLaboratorios = [], patientId, internacionId, add, update, del, addAcidoBase, addOtroLaboratorio, canEdit, portalNode}: {
|
||||
lab: Laboratorio[];
|
||||
otrosLaboratorios?: OtroLaboratorio[];
|
||||
patientId: string;
|
||||
internacionId: string;
|
||||
add: (l: Omit<Laboratorio, 'id'>) => void;
|
||||
update: (id: string, data: Partial<Laboratorio>) => void;
|
||||
del: (id: string) => void;
|
||||
addAcidoBase?: (a: Omit<AcidoBase, 'id'>) => void;
|
||||
addOtroLaboratorio?: (o: Omit<OtroLaboratorio, 'id'>) => void;
|
||||
canEdit?: boolean;
|
||||
portalNode?: HTMLDivElement | null;
|
||||
}) {
|
||||
const [dialog, setDialog] = useState(false);
|
||||
const [obsDialog, setObsDialog] = useState(false);
|
||||
|
||||
const [otrosDialog, setOtrosDialog] = useState(false);
|
||||
const [nuevaObsFecha, setNuevaObsFecha] = useState(getLocalToday());
|
||||
const [nuevaObsTexto, setNuevaObsTexto] = useState('');
|
||||
|
||||
const [evolDialog, setEvolDialog] = useState(false);
|
||||
const [edit, setEdit] = useState<Laboratorio | null>(null);
|
||||
const [obsLab, setObsLab] = useState<Laboratorio | null>(null);
|
||||
@@ -1387,13 +1405,27 @@ function SeccionLaboratorios({ lab, patientId, internacionId, add, update, del,
|
||||
addAcidoBase({ ...importAcidoBase, pacienteId: patientId, internacionId, fecha: importFecha, hora: importHora });
|
||||
}
|
||||
|
||||
add({
|
||||
pacienteId: patientId,
|
||||
fecha: importFecha,
|
||||
hora: importHora,
|
||||
resultados: importResultados,
|
||||
observaciones: importObservaciones
|
||||
});
|
||||
if (importResultados.length > 0) {
|
||||
add({
|
||||
pacienteId: patientId,
|
||||
internacionId,
|
||||
fecha: importFecha,
|
||||
hora: importHora,
|
||||
resultados: importResultados,
|
||||
});
|
||||
}
|
||||
|
||||
if (importObservaciones && importObservaciones.trim() !== '') {
|
||||
if (addOtroLaboratorio) {
|
||||
addOtroLaboratorio({
|
||||
pacienteId: patientId,
|
||||
internacionId,
|
||||
fecha: importFecha,
|
||||
hora: importHora,
|
||||
observaciones: importObservaciones
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setImportDialog(false);
|
||||
setImportTexto('');
|
||||
@@ -1457,9 +1489,9 @@ function SeccionLaboratorios({ lab, patientId, internacionId, add, update, del,
|
||||
|
||||
return (
|
||||
<div className="space-y-4 w-full max-w-full min-w-0">
|
||||
<div className="flex justify-between">
|
||||
<div className="flex gap-2">
|
||||
<DropdownMenu>
|
||||
{portalNode ? createPortal(
|
||||
<>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline">
|
||||
<List className="h-4 w-4 mr-2" />
|
||||
@@ -1478,15 +1510,43 @@ function SeccionLaboratorios({ lab, patientId, internacionId, add, update, del,
|
||||
<TrendingUp className="h-4 w-4 mr-2" />
|
||||
Evolución
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setOtrosDialog(true)}>
|
||||
<Eye className="h-4 w-4 mr-2" />
|
||||
Ver Otros
|
||||
</DropdownMenuItem>
|
||||
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Button variant="outline" onClick={() => setOtrosDialog(true)}><Eye className="h-4 w-4 mr-2" />Otros</Button>
|
||||
<Button variant="outline" onClick={() => { setImportFecha(getLocalToday()); setImportHora(new Date().toTimeString().slice(0, 5)); setImportTexto(''); setImportResultados([]); setImportObservaciones(''); setImportDialog(true); }}><FileText className="h-4 w-4 mr-2" />Importar</Button>
|
||||
</>,
|
||||
portalNode
|
||||
) : (
|
||||
<div className="flex justify-between mb-4">
|
||||
<div className="flex gap-2">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline">
|
||||
<List className="h-4 w-4 mr-2" />
|
||||
Acciones
|
||||
<ChevronDown className="h-4 w-4 ml-2" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="w-48">
|
||||
{canEdit && (
|
||||
<DropdownMenuItem onClick={() => { reset(); setDialog(true); }}>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Nuevo
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem onClick={() => { setParametroEvolucion('Hematocrito'); setEvolDialog(true); }}>
|
||||
<TrendingUp className="h-4 w-4 mr-2" />
|
||||
Evolución
|
||||
</DropdownMenuItem>
|
||||
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Button variant="outline" onClick={() => setOtrosDialog(true)}><Eye className="h-4 w-4 mr-2" />Otros</Button>
|
||||
<Button variant="outline" onClick={() => { setImportFecha(getLocalToday()); setImportHora(new Date().toTimeString().slice(0, 5)); setImportTexto(''); setImportResultados([]); setImportObservaciones(''); setImportDialog(true); }}><FileText className="h-4 w-4 mr-2" />Importar</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Dialog open={dialog} onOpenChange={setDialog}>
|
||||
<DialogContent className="sm:max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader><DialogTitle>{edit ? 'Editar' : 'Nuevo'} Laboratorio</DialogTitle></DialogHeader>
|
||||
@@ -1564,26 +1624,79 @@ function SeccionLaboratorios({ lab, patientId, internacionId, add, update, del,
|
||||
</Dialog>
|
||||
|
||||
<Dialog open={otrosDialog} onOpenChange={setOtrosDialog}>
|
||||
<DialogContent className="sm:max-w-xl max-h-[85vh] overflow-y-auto" aria-describedby={undefined}>
|
||||
<DialogContent className="sm:max-w-2xl max-h-[85vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Otros / Observaciones de Laboratorio</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 my-2">
|
||||
{labsConObservaciones.length === 0 ? (
|
||||
<p className="text-sm text-gray-500 text-center py-6">No hay observaciones registradas.</p>
|
||||
) : (
|
||||
labsConObservaciones.map(l => (
|
||||
<div key={l.id} className="p-3 bg-gray-50 dark:bg-gray-800 rounded-lg border border-gray-100 dark:border-gray-700 space-y-2">
|
||||
<div className="flex justify-between items-center text-xs text-gray-500 dark:text-gray-400 font-medium">
|
||||
<span>{formatDateDDMMYYYY(l.fecha)} {l.hora || ''}</span>
|
||||
<Badge variant="secondary" className="text-[10px] px-1.5 py-0.5">Obs</Badge>
|
||||
|
||||
{canEdit && (
|
||||
<div className="bg-gray-50 dark:bg-gray-900 p-4 rounded-lg border border-gray-200 dark:border-gray-700 space-y-3 mb-6">
|
||||
<h4 className="text-sm font-semibold text-gray-700 dark:text-gray-300">Agregar Nuevo Registro (Otros)</h4>
|
||||
<div className="flex flex-col sm:flex-row gap-3">
|
||||
<div className="w-full sm:w-1/3">
|
||||
<Label>Fecha</Label>
|
||||
<Input type="date" value={nuevaObsFecha} onChange={e => setNuevaObsFecha(e.target.value)} />
|
||||
</div>
|
||||
<div className="w-full sm:w-2/3">
|
||||
<Label>Observaciones / Determinaciones</Label>
|
||||
<Textarea
|
||||
placeholder="Ingrese los detalles, resultados, etc..."
|
||||
value={nuevaObsTexto}
|
||||
onChange={e => setNuevaObsTexto(e.target.value)}
|
||||
className="min-h-[80px]"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-sm text-gray-700 dark:text-gray-300 whitespace-pre-wrap font-sans">{l.observaciones}</p>
|
||||
</div>
|
||||
))
|
||||
<div className="flex justify-end">
|
||||
<Button onClick={() => {
|
||||
if (addOtroLaboratorio && nuevaObsTexto.trim() !== '') {
|
||||
addOtroLaboratorio({
|
||||
pacienteId: patientId,
|
||||
internacionId,
|
||||
fecha: nuevaObsFecha,
|
||||
hora: new Date().toTimeString().slice(0, 5),
|
||||
observaciones: nuevaObsTexto
|
||||
});
|
||||
setNuevaObsTexto('');
|
||||
setNuevaObsFecha(getLocalToday());
|
||||
toast.success('Registro guardado correctamente');
|
||||
}
|
||||
}} disabled={!nuevaObsTexto.trim()}>
|
||||
<Save className="h-4 w-4 mr-2" /> Guardar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<h4 className="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3 border-b pb-2">Registros</h4>
|
||||
<div className="space-y-3 max-h-[40vh] overflow-y-auto pr-2">
|
||||
{[
|
||||
...labsConObservaciones.map(l => ({ id: l.id, fecha: l.fecha, hora: l.hora, observaciones: l.observaciones, source: 'lab' })),
|
||||
...(otrosLaboratorios || []).filter(o => o.pacienteId === patientId).map(o => ({ id: o.id, fecha: o.fecha, hora: o.hora, observaciones: o.observaciones, source: 'otros' }))
|
||||
].sort((a, b) => {
|
||||
const dateA = new Date(a.fecha + 'T' + (a.hora || '00:00')).getTime();
|
||||
const dateB = new Date(b.fecha + 'T' + (b.hora || '00:00')).getTime();
|
||||
return dateB - dateA;
|
||||
}).map((item, idx) => (
|
||||
<div key={item.id + idx} className="p-3 bg-gray-50 dark:bg-gray-800 rounded-lg border border-gray-100 dark:border-gray-700 space-y-2">
|
||||
<div className="flex justify-between items-center text-xs text-gray-500 dark:text-gray-400 font-medium">
|
||||
<span>{formatDateDDMMYYYY(item.fecha)} {item.hora || ''}</span>
|
||||
<Badge variant="secondary" className="text-[10px] px-1.5 py-0.5">{item.source === 'lab' ? 'Obs. de Lab' : 'Otros'}</Badge>
|
||||
</div>
|
||||
<p className="text-sm text-gray-700 dark:text-gray-300 whitespace-pre-wrap font-sans">{item.observaciones}</p>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{(labsConObservaciones.length === 0 && (!otrosLaboratorios || otrosLaboratorios.filter(o => o.pacienteId === patientId).length === 0)) && (
|
||||
<p className="text-sm text-gray-500 text-center py-6">No hay registros adicionales.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className="flex justify-end gap-2 mt-4">
|
||||
<div className="flex justify-end gap-2 mt-2">
|
||||
<Button onClick={() => setOtrosDialog(false)}>Cerrar</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
@@ -119,6 +119,15 @@ export interface ResultadoLaboratorio {
|
||||
estado: 'Normal' | 'Alto' | 'Bajo' | 'Crítico';
|
||||
}
|
||||
|
||||
export interface OtroLaboratorio {
|
||||
id: string;
|
||||
pacienteId: string;
|
||||
internacionId?: string;
|
||||
fecha: string;
|
||||
hora?: string;
|
||||
observaciones: string;
|
||||
}
|
||||
|
||||
export interface Glucemia {
|
||||
id: string;
|
||||
pacienteId: string;
|
||||
|
||||
Reference in New Issue
Block a user