Actualizar Otras Determinaciones - Laboratorio con categorías y diseño mejorado
This commit is contained in:
@@ -60,7 +60,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@
|
|||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
|
||||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
|
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, OtroLaboratorio } 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 { cn, formatDateDDMMYYYY, getLocalToday } from '@/lib/utils';
|
||||||
import { SeccionIndicaciones } from './SeccionIndicaciones';
|
import { SeccionIndicaciones } from './SeccionIndicaciones';
|
||||||
|
|
||||||
interface HistoriaClinicaProps {
|
interface HistoriaClinicaProps {
|
||||||
@@ -1031,21 +1031,56 @@ function SeccionLaboratorios({ lab, otrosLaboratorios = [], patientId, internaci
|
|||||||
const [dialog, setDialog] = useState(false);
|
const [dialog, setDialog] = useState(false);
|
||||||
const [obsDialog, setObsDialog] = useState(false);
|
const [obsDialog, setObsDialog] = useState(false);
|
||||||
|
|
||||||
|
const CATEGORIAS_OTROS = [
|
||||||
|
"Perfil Lipídico",
|
||||||
|
"Perfil Tiroideo",
|
||||||
|
"Autoinmunidad",
|
||||||
|
"Toracocentesis",
|
||||||
|
"Líquido Ascitico",
|
||||||
|
"LCR",
|
||||||
|
"Otros"
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const getCategoriaBadgeStyle = (cat?: string) => {
|
||||||
|
switch (cat) {
|
||||||
|
case 'Perfil Lipídico':
|
||||||
|
return 'bg-amber-100 text-amber-800 dark:bg-amber-950 dark:text-amber-300 border-amber-200 dark:border-amber-800';
|
||||||
|
case 'Perfil Tiroideo':
|
||||||
|
return 'bg-purple-100 text-purple-800 dark:bg-purple-950 dark:text-purple-300 border-purple-200 dark:border-purple-800';
|
||||||
|
case 'Autoinmunidad':
|
||||||
|
return 'bg-rose-100 text-rose-800 dark:bg-rose-950 dark:text-rose-300 border-rose-200 dark:border-rose-800';
|
||||||
|
case 'Toracocentesis':
|
||||||
|
return 'bg-cyan-100 text-cyan-800 dark:bg-cyan-950 dark:text-cyan-300 border-cyan-200 dark:border-cyan-800';
|
||||||
|
case 'Líquido Ascitico':
|
||||||
|
return 'bg-emerald-100 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-300 border-emerald-200 dark:border-emerald-800';
|
||||||
|
case 'LCR':
|
||||||
|
return 'bg-blue-100 text-blue-800 dark:bg-blue-950 dark:text-blue-300 border-blue-200 dark:border-blue-800';
|
||||||
|
case 'Obs. de Lab':
|
||||||
|
return 'bg-indigo-100 text-indigo-800 dark:bg-indigo-950 dark:text-indigo-300 border-indigo-200 dark:border-indigo-800';
|
||||||
|
default:
|
||||||
|
return 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200 border-gray-200 dark:border-gray-700';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const [otrosDialog, setOtrosDialog] = useState(false);
|
const [otrosDialog, setOtrosDialog] = useState(false);
|
||||||
const [nuevaObsFecha, setNuevaObsFecha] = useState(getLocalToday());
|
const [nuevaObsFecha, setNuevaObsFecha] = useState(getLocalToday());
|
||||||
const [nuevaObsTexto, setNuevaObsTexto] = useState('');
|
const [nuevaObsTexto, setNuevaObsTexto] = useState('');
|
||||||
|
const [nuevaObsCategoria, setNuevaObsCategoria] = useState<string>('Otros');
|
||||||
|
const [mostrarNuevoFormOtros, setMostrarNuevoFormOtros] = useState<boolean>(false);
|
||||||
|
|
||||||
const [editOtrosDialog, setEditOtrosDialog] = useState(false);
|
const [editOtrosDialog, setEditOtrosDialog] = useState(false);
|
||||||
const [editOtrosItem, setEditOtrosItem] = useState<{ id: string; fecha: string; hora: string; observaciones: string; source: 'lab' | 'otros' } | null>(null);
|
const [editOtrosItem, setEditOtrosItem] = useState<{ id: string; fecha: string; hora: string; observaciones: string; categoria?: string; source: 'lab' | 'otros' } | null>(null);
|
||||||
const [editOtrosFecha, setEditOtrosFecha] = useState('');
|
const [editOtrosFecha, setEditOtrosFecha] = useState('');
|
||||||
const [editOtrosHora, setEditOtrosHora] = useState('');
|
const [editOtrosHora, setEditOtrosHora] = useState('');
|
||||||
const [editOtrosTexto, setEditOtrosTexto] = useState('');
|
const [editOtrosTexto, setEditOtrosTexto] = useState('');
|
||||||
|
const [editOtrosCategoria, setEditOtrosCategoria] = useState<string>('Otros');
|
||||||
|
|
||||||
const handleStartEditOtros = (item: { id: string; fecha: string; hora?: string; observaciones?: string; source: 'lab' | 'otros' }) => {
|
const handleStartEditOtros = (item: { id: string; fecha: string; hora?: string; observaciones?: string; categoria?: string; source: 'lab' | 'otros' }) => {
|
||||||
setEditOtrosItem({ id: item.id, fecha: item.fecha, hora: item.hora || '00:00', observaciones: item.observaciones || '', source: item.source });
|
setEditOtrosItem({ id: item.id, fecha: item.fecha, hora: item.hora || '00:00', observaciones: item.observaciones || '', categoria: item.categoria || 'Otros', source: item.source });
|
||||||
setEditOtrosFecha(item.fecha);
|
setEditOtrosFecha(item.fecha);
|
||||||
setEditOtrosHora(item.hora || '00:00');
|
setEditOtrosHora(item.hora || '00:00');
|
||||||
setEditOtrosTexto(item.observaciones || '');
|
setEditOtrosTexto(item.observaciones || '');
|
||||||
|
setEditOtrosCategoria(item.categoria || 'Otros');
|
||||||
setEditOtrosDialog(true);
|
setEditOtrosDialog(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1056,7 +1091,8 @@ function SeccionLaboratorios({ lab, otrosLaboratorios = [], patientId, internaci
|
|||||||
updateOtroLaboratorio(editOtrosItem.id, {
|
updateOtroLaboratorio(editOtrosItem.id, {
|
||||||
fecha: editOtrosFecha,
|
fecha: editOtrosFecha,
|
||||||
hora: editOtrosHora,
|
hora: editOtrosHora,
|
||||||
observaciones: editOtrosTexto
|
observaciones: editOtrosTexto,
|
||||||
|
categoria: editOtrosCategoria
|
||||||
});
|
});
|
||||||
toast.success('Registro de Otros Laboratorios actualizado');
|
toast.success('Registro de Otros Laboratorios actualizado');
|
||||||
}
|
}
|
||||||
@@ -1776,39 +1812,89 @@ function SeccionLaboratorios({ lab, otrosLaboratorios = [], patientId, internaci
|
|||||||
<div className="space-y-4 my-2">
|
<div className="space-y-4 my-2">
|
||||||
|
|
||||||
{canEdit && (
|
{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">
|
<div className="flex justify-between items-center bg-gray-50 dark:bg-gray-800/60 p-3 rounded-lg border border-gray-200 dark:border-gray-700">
|
||||||
<h4 className="text-sm font-semibold text-gray-700 dark:text-gray-300">Agregar Nuevo Registro (Otros)</h4>
|
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
<div className="flex flex-col sm:flex-row gap-3">
|
¿Desea agregar una nueva observación?
|
||||||
<div className="w-full sm:w-1/3">
|
</span>
|
||||||
<Label>Fecha</Label>
|
<Button
|
||||||
<Input type="date" value={nuevaObsFecha} onChange={e => setNuevaObsFecha(e.target.value)} />
|
size="sm"
|
||||||
|
variant={mostrarNuevoFormOtros ? "outline" : "default"}
|
||||||
|
onClick={() => setMostrarNuevoFormOtros(!mostrarNuevoFormOtros)}
|
||||||
|
>
|
||||||
|
{mostrarNuevoFormOtros ? (
|
||||||
|
<>
|
||||||
|
<X className="h-4 w-4 mr-1.5" /> Ocultar Formulario
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Plus className="h-4 w-4 mr-1.5" /> Nuevo Registro
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{canEdit && mostrarNuevoFormOtros && (
|
||||||
|
<div className="bg-blue-50/40 dark:bg-gray-900 p-4 rounded-lg border border-blue-200 dark:border-gray-700 space-y-3 mb-4 animate-in fade-in duration-200">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<h4 className="text-sm font-semibold text-gray-800 dark:text-gray-200">Agregar Nuevo Registro</h4>
|
||||||
|
<Button size="icon" variant="ghost" className="h-6 w-6 text-gray-500 hover:text-gray-700" onClick={() => setMostrarNuevoFormOtros(false)}>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
|
<div>
|
||||||
|
<Label className="text-xs font-semibold text-gray-700 dark:text-gray-300">Categoría</Label>
|
||||||
|
<Select value={nuevaObsCategoria} onValueChange={setNuevaObsCategoria}>
|
||||||
|
<SelectTrigger className="h-9 bg-white dark:bg-gray-800">
|
||||||
|
<SelectValue placeholder="Seleccionar categoría" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{CATEGORIAS_OTROS.map(cat => (
|
||||||
|
<SelectItem key={cat} value={cat}>{cat}</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full sm:w-2/3">
|
<div>
|
||||||
<Label>Observaciones / Determinaciones</Label>
|
<Label className="text-xs font-semibold text-gray-700 dark:text-gray-300">Fecha</Label>
|
||||||
<Textarea
|
<Input type="date" className="h-9 bg-white dark:bg-gray-800" value={nuevaObsFecha} onChange={e => setNuevaObsFecha(e.target.value)} />
|
||||||
placeholder="Ingrese los detalles, resultados, etc..."
|
|
||||||
value={nuevaObsTexto}
|
|
||||||
onChange={e => setNuevaObsTexto(e.target.value)}
|
|
||||||
className="min-h-[80px]"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end">
|
|
||||||
<Button onClick={() => {
|
<div>
|
||||||
|
<Label className="text-xs font-semibold text-gray-700 dark:text-gray-300">Observaciones / Determinaciones</Label>
|
||||||
|
<Textarea
|
||||||
|
placeholder="Ingrese los detalles, resultados, etc..."
|
||||||
|
value={nuevaObsTexto}
|
||||||
|
onChange={e => setNuevaObsTexto(e.target.value)}
|
||||||
|
className="min-h-[80px] bg-white dark:bg-gray-800"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-2 pt-1">
|
||||||
|
<Button variant="outline" size="sm" onClick={() => setMostrarNuevoFormOtros(false)}>
|
||||||
|
Cancelar
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" onClick={() => {
|
||||||
if (addOtroLaboratorio && nuevaObsTexto.trim() !== '') {
|
if (addOtroLaboratorio && nuevaObsTexto.trim() !== '') {
|
||||||
addOtroLaboratorio({
|
addOtroLaboratorio({
|
||||||
pacienteId: patientId,
|
pacienteId: patientId,
|
||||||
internacionId,
|
internacionId,
|
||||||
fecha: nuevaObsFecha,
|
fecha: nuevaObsFecha,
|
||||||
hora: new Date().toTimeString().slice(0, 5),
|
hora: new Date().toTimeString().slice(0, 5),
|
||||||
observaciones: nuevaObsTexto
|
observaciones: nuevaObsTexto,
|
||||||
|
categoria: nuevaObsCategoria
|
||||||
});
|
});
|
||||||
setNuevaObsTexto('');
|
setNuevaObsTexto('');
|
||||||
setNuevaObsFecha(getLocalToday());
|
setNuevaObsFecha(getLocalToday());
|
||||||
|
setNuevaObsCategoria('Otros');
|
||||||
|
setMostrarNuevoFormOtros(false);
|
||||||
toast.success('Registro guardado correctamente');
|
toast.success('Registro guardado correctamente');
|
||||||
}
|
}
|
||||||
}} disabled={!nuevaObsTexto.trim()}>
|
}} disabled={!nuevaObsTexto.trim()}>
|
||||||
<Save className="h-4 w-4 mr-2" /> Guardar
|
<Save className="h-4 w-4 mr-1.5" /> Guardar
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1816,26 +1902,28 @@ function SeccionLaboratorios({ lab, otrosLaboratorios = [], patientId, internaci
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3 border-b pb-2">Registros</h4>
|
<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">
|
<div className="space-y-3 max-h-[45vh] overflow-y-auto pr-2">
|
||||||
{[
|
{[
|
||||||
...labsConObservaciones.map(l => ({ id: l.id, fecha: l.fecha, hora: l.hora, observaciones: l.observaciones, source: 'lab' })),
|
...labsConObservaciones.map(l => ({ id: l.id, fecha: l.fecha, hora: l.hora, observaciones: l.observaciones, categoria: 'Obs. de Lab', source: 'lab' as const })),
|
||||||
...(otrosLaboratorios || []).filter(o => o.pacienteId === patientId).map(o => ({ id: o.id, fecha: o.fecha, hora: o.hora, observaciones: o.observaciones, source: 'otros' }))
|
...(otrosLaboratorios || []).filter(o => o.pacienteId === patientId).map(o => ({ id: o.id, fecha: o.fecha, hora: o.hora, observaciones: o.observaciones, categoria: o.categoria || 'Otros', source: 'otros' as const }))
|
||||||
].sort((a, b) => {
|
].sort((a, b) => {
|
||||||
const dateA = new Date(a.fecha + 'T' + (a.hora || '00:00')).getTime();
|
const dateA = new Date((a.fecha || '1970-01-01') + 'T' + (a.hora && a.hora.trim() ? a.hora : '00:00')).getTime();
|
||||||
const dateB = new Date(b.fecha + 'T' + (b.hora || '00:00')).getTime();
|
const dateB = new Date((b.fecha || '1970-01-01') + 'T' + (b.hora && b.hora.trim() ? b.hora : '00:00')).getTime();
|
||||||
return dateB - dateA;
|
return dateB - dateA;
|
||||||
}).map((item, idx) => (
|
}).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 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">
|
<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>
|
<span>{formatDateDDMMYYYY(item.fecha)} {item.hora || ''}</span>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Badge variant="secondary" className="text-[10px] px-1.5 py-0.5">{item.source === 'lab' ? 'Obs. de Lab' : 'Otros'}</Badge>
|
<Badge variant="outline" className={cn("text-[11px] font-semibold px-2 py-0.5 border", getCategoriaBadgeStyle(item.categoria))}>
|
||||||
|
{item.categoria}
|
||||||
|
</Badge>
|
||||||
{canEdit && (
|
{canEdit && (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Button size="icon" variant="ghost" className="h-6 w-6 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700" onClick={() => handleStartEditOtros(item as OtroLaboratorio)} title="Editar">
|
<Button size="icon" variant="ghost" className="h-6 w-6 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700" onClick={() => handleStartEditOtros(item)} title="Editar">
|
||||||
<Pencil className="h-3.5 w-3.5" />
|
<Pencil className="h-3.5 w-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="icon" variant="ghost" className="h-6 w-6 text-red-600 hover:text-red-700 hover:bg-red-50 dark:hover:bg-red-950/40" onClick={() => handleDeleteOtros(item as OtroLaboratorio)} title="Eliminar">
|
<Button size="icon" variant="ghost" className="h-6 w-6 text-red-600 hover:text-red-700 hover:bg-red-50 dark:hover:bg-red-950/40" onClick={() => handleDeleteOtros(item)} title="Eliminar">
|
||||||
<Trash2 className="h-3.5 w-3.5" />
|
<Trash2 className="h-3.5 w-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -1862,21 +1950,36 @@ function SeccionLaboratorios({ lab, otrosLaboratorios = [], patientId, internaci
|
|||||||
<Dialog open={editOtrosDialog} onOpenChange={setEditOtrosDialog}>
|
<Dialog open={editOtrosDialog} onOpenChange={setEditOtrosDialog}>
|
||||||
<DialogContent className="sm:max-w-md">
|
<DialogContent className="sm:max-w-md">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Editar Registro de Otros Laboratorios</DialogTitle>
|
<DialogTitle>Editar Registro</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-4 my-2">
|
<div className="space-y-4 my-2">
|
||||||
|
{editOtrosItem?.source === 'otros' && (
|
||||||
|
<div>
|
||||||
|
<Label className="text-xs font-semibold text-gray-700 dark:text-gray-300">Categoría</Label>
|
||||||
|
<Select value={editOtrosCategoria} onValueChange={setEditOtrosCategoria}>
|
||||||
|
<SelectTrigger className="h-9">
|
||||||
|
<SelectValue placeholder="Seleccionar categoría" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{CATEGORIAS_OTROS.map(cat => (
|
||||||
|
<SelectItem key={cat} value={cat}>{cat}</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<div className="w-1/2">
|
<div className="w-1/2">
|
||||||
<Label>Fecha</Label>
|
<Label className="text-xs font-semibold text-gray-700 dark:text-gray-300">Fecha</Label>
|
||||||
<Input type="date" value={editOtrosFecha} onChange={e => setEditOtrosFecha(e.target.value)} />
|
<Input type="date" value={editOtrosFecha} onChange={e => setEditOtrosFecha(e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
<div className="w-1/2">
|
<div className="w-1/2">
|
||||||
<Label>Hora</Label>
|
<Label className="text-xs font-semibold text-gray-700 dark:text-gray-300">Hora</Label>
|
||||||
<Input type="time" value={editOtrosHora} onChange={e => setEditOtrosHora(e.target.value)} />
|
<Input type="time" value={editOtrosHora} onChange={e => setEditOtrosHora(e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label>Observaciones / Determinaciones</Label>
|
<Label className="text-xs font-semibold text-gray-700 dark:text-gray-300">Observaciones / Determinaciones</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
placeholder="Ingrese los detalles, resultados, etc..."
|
placeholder="Ingrese los detalles, resultados, etc..."
|
||||||
value={editOtrosTexto}
|
value={editOtrosTexto}
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ export interface OtroLaboratorio {
|
|||||||
fecha: string;
|
fecha: string;
|
||||||
hora?: string;
|
hora?: string;
|
||||||
observaciones: string;
|
observaciones: string;
|
||||||
|
categoria?: 'Perfil Lipídico' | 'Perfil Tiroideo' | 'Autoinmunidad' | 'Toracocentesis' | 'Líquido Ascitico' | 'LCR' | 'Otros' | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Glucemia {
|
export interface Glucemia {
|
||||||
|
|||||||
Reference in New Issue
Block a user