Add ATB tab for antibiotic treatment tracking
This commit is contained in:
@@ -22,7 +22,7 @@ import {
|
||||
FileText,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
ChevronRight,
|
||||
List,
|
||||
FileDown,
|
||||
Thermometer,
|
||||
Heart,
|
||||
@@ -51,7 +51,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, AcidoBase, Cultivo, Paciente, ResultadoLaboratorio, Internacion, Evolucion, Cama, EstudioComplementario, Interconsulta } from '@/types';
|
||||
import type { Laboratorio, AcidoBase, Cultivo, Paciente, ResultadoLaboratorio, Internacion, Evolucion, Cama, EstudioComplementario, Interconsulta, ATB } from '@/types';
|
||||
import { formatDateDDMMYYYY } from '@/lib/utils';
|
||||
|
||||
interface HistoriaClinicaProps {
|
||||
@@ -65,6 +65,7 @@ interface HistoriaClinicaProps {
|
||||
cultivos: Cultivo[];
|
||||
estudiosComplementarios: EstudioComplementario[];
|
||||
interconsultas: Interconsulta[];
|
||||
atb: ATB[];
|
||||
onAgregarEvolucion: (evolucion: Omit<Evolucion, 'id'>) => void;
|
||||
onActualizarEvolucion: (id: string, datos: Partial<Evolucion>) => void;
|
||||
onEliminarEvolucion: (id: string) => void;
|
||||
@@ -84,6 +85,9 @@ interface HistoriaClinicaProps {
|
||||
onAgregarInterconsulta: (interconsulta: Omit<Interconsulta, 'id'>) => void;
|
||||
onActualizarInterconsulta: (id: string, datos: Partial<Interconsulta>) => void;
|
||||
onEliminarInterconsulta: (id: string) => void;
|
||||
onAgregarATB: (atb: Omit<ATB, 'id'>) => void;
|
||||
onActualizarATB: (id: string, datos: Partial<ATB>) => void;
|
||||
onEliminarATB: (id: string) => void;
|
||||
onActualizarInternacion: (id: string, datos: Partial<Internacion>) => void;
|
||||
onActualizarCama: (id: string, datos: Partial<Cama>) => void;
|
||||
onVolver: () => void;
|
||||
@@ -192,6 +196,10 @@ export function HistoriaClinica({
|
||||
onAgregarInterconsulta,
|
||||
onActualizarInterconsulta,
|
||||
onEliminarInterconsulta,
|
||||
atb,
|
||||
onAgregarATB,
|
||||
onActualizarATB,
|
||||
onEliminarATB,
|
||||
onActualizarInternacion,
|
||||
onActualizarCama,
|
||||
onVolver,
|
||||
@@ -473,6 +481,13 @@ export function HistoriaClinica({
|
||||
<span className="hidden sm:inline">Evoluciones</span>
|
||||
({evoluciones.length})
|
||||
</TabsTrigger>
|
||||
|
||||
<TabsTrigger value="indicaciones" className="flex-shrink-0 flex items-center gap-1 text-xs sm:text-sm">
|
||||
<List className="h-3 w-3 sm:h-4 sm:w-4" />
|
||||
<span className="hidden sm:inline">Indicaciones</span>
|
||||
|
||||
</TabsTrigger>
|
||||
|
||||
<TabsTrigger value="laboratorios" className="flex-shrink-0 flex items-center gap-1 text-xs sm:text-sm">
|
||||
<FlaskConical className="h-3 w-3 sm:h-4 sm:w-4" />
|
||||
<span className="hidden sm:inline">Laboratorio</span>
|
||||
@@ -488,6 +503,11 @@ export function HistoriaClinica({
|
||||
<span className="hidden sm:inline">Cultivos</span>
|
||||
({cultivos.length})
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="atb" className="flex-shrink-0 flex items-center gap-1 text-xs sm:text-sm">
|
||||
<Pill className="h-3 w-3 sm:h-4 sm:w-4" />
|
||||
<span className="hidden sm:inline">ATB</span>
|
||||
({atb.length})
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="estudios" className="flex-shrink-0 flex items-center gap-1 text-xs sm:text-sm">
|
||||
<ClipboardList className="h-3 w-3 sm:h-4 sm:w-4" />
|
||||
<span className="hidden sm:inline">Estudios</span>
|
||||
@@ -519,10 +539,14 @@ export function HistoriaClinica({
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="atb" className="mt-4">
|
||||
<div className="text-center py-8 text-gray-400">
|
||||
<Pill className="h-12 w-12 mx-auto mb-2 opacity-50" />
|
||||
<p>Sección ATB en desarrollo</p>
|
||||
</div>
|
||||
<SeccionATB
|
||||
atb={atb}
|
||||
internacionId={internacion.id}
|
||||
pacienteId={paciente.id}
|
||||
add={onAgregarATB}
|
||||
update={onActualizarATB}
|
||||
del={onEliminarATB}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="estudios" className="mt-4">
|
||||
@@ -679,10 +703,9 @@ function SeccionLaboratorios({ lab, patientId, add, update, del, addAcidoBase }:
|
||||
const observacionesExtra: string[] = [];
|
||||
|
||||
const mapeoParametros: Record<string, { nombre: string; unidad: string; esPrincipal: boolean }> = {
|
||||
'hematíes': { nombre: 'Hematíes', unidad: '10⁶/µl', esPrincipal: false },
|
||||
|
||||
'hematocrito': { nombre: 'Hematocrito', unidad: '%', esPrincipal: true },
|
||||
'hemoglobina corpuscular media': { nombre: 'HCM', unidad: 'pg', esPrincipal: false },
|
||||
'concentración de hemoglobina media': { nombre: 'CHCM', unidad: 'g/dL', esPrincipal: false },
|
||||
'hemoglobina': { nombre: 'Hemoglobina', unidad: 'g/dL', esPrincipal: true },
|
||||
'volumen corpuscular medio': { nombre: 'VCM', unidad: 'fL', esPrincipal: false },
|
||||
'rdw': { nombre: 'RDW', unidad: '%', esPrincipal: false },
|
||||
@@ -1433,6 +1456,7 @@ function SeccionAcidosBase({ ab, patientId, add, update, del }: {
|
||||
const [lactato, setLactato] = useState('');
|
||||
const [fio2, setFio2] = useState('');
|
||||
const [interpretacion, setInterpretacion] = useState('');
|
||||
const [eabSortAsc, setEabSortAsc] = useState(false);
|
||||
|
||||
const reset = () => {
|
||||
setFecha(new Date().toISOString().split('T')[0]);
|
||||
@@ -1555,38 +1579,71 @@ function SeccionAcidosBase({ ab, patientId, add, update, del }: {
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
{abFiltered.length === 0 ? <div className="text-center py-8 text-gray-400"><Activity className="h-12 w-12 mx-auto mb-2 opacity-50" /><p>No hay gasometrías</p></div> :
|
||||
abFiltered.map(a => {
|
||||
const phSt = getEstadoParametro('ph', a.ph);
|
||||
return (
|
||||
<Card key={a.id} className="mb-2"><CardContent className="p-4">
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-start justify-between">
|
||||
<div><p className="font-medium">{a.fecha} {a.hora}</p></div>
|
||||
<div className="flex items-end gap-2">
|
||||
|
||||
<Badge variant="outline" className="text-xs">{a.fio2 && a.po2 && <span className="mr-1">PaFiO2: {Math.round(a.po2 / a.fio2 * 100) / 100}</span>}</Badge>
|
||||
<Badge variant="outline" className="text-xs">{a.fio2 && <span className="mr-1">FiO₂:{a.fio2}</span>}</Badge>
|
||||
<Badge className={getColorPh(a.ph)}>pH: {a.ph}</Badge>
|
||||
<Button size="sm" variant="ghost" className="text-blue-600" onClick={() => loadEdit(a)}><Edit /></Button>
|
||||
<Button size="sm" variant="ghost" className="text-red-600" onClick={() => del(a.id)}><Trash2 /></Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 sm:grid-cols-8 gap-2">
|
||||
<div className="bg-gray-50 p-2 rounded text-center"><p className="text-xs text-gray-500">pH</p><p className={`font-bold ${phSt.color}`}>{a.ph}</p></div>
|
||||
<div className="bg-gray-50 p-2 rounded text-center"><p className="text-xs text-gray-500">pCO2</p><p className="font-bold">{a.pco2}</p></div>
|
||||
<div className="bg-gray-50 p-2 rounded text-center"><p className="text-xs text-gray-500">pO2</p><p className="font-bold">{a.po2}</p></div>
|
||||
<div className="bg-gray-50 p-2 rounded text-center"><p className="text-xs text-gray-500">HCO3</p><p className="font-bold">{a.hco3}</p></div>
|
||||
<div className="bg-gray-50 p-2 rounded text-center"><p className="text-xs text-gray-500">BE</p><p className="font-bold">{a.be}</p></div>
|
||||
<div className="bg-gray-50 p-2 rounded text-center"><p className="text-xs text-gray-500">SatO2</p><p className="font-bold">{a.sato2}%</p></div>
|
||||
{a.lactato && <div className="bg-gray-50 p-2 rounded text-center"><p className="text-xs text-gray-500">Lactato</p><p className="font-bold">{a.lactato}</p></div>}
|
||||
|
||||
{a.fio2 && <div className="bg-gray-50 p-2 rounded text-center"><p className="text-xs text-gray-500">FiO2</p><p className="font-bold">{a.fio2}</p></div>}
|
||||
</div>
|
||||
{a.interpretacion && <div className="bg-teal-50 p-3 rounded-lg border border-teal-200"><p className="text-sm font-medium text-teal-800">Interpretación: {a.interpretacion}</p></div>}
|
||||
</div>
|
||||
</CardContent></Card>
|
||||
);
|
||||
})
|
||||
|
||||
<div className="grid grid-cols-1 w-full">
|
||||
<div className="w-full overflow-x-auto rounded-md border pb-2">
|
||||
|
||||
<Table className="w-full min-w-max text-sm">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="cursor-pointer hover:bg-muted" onClick={() => setEabSortAsc(!eabSortAsc)}>
|
||||
Fecha {eabSortAsc ? '↑' : '↓'}
|
||||
</TableHead>
|
||||
<TableHead>pH</TableHead>
|
||||
<TableHead>pCO2</TableHead>
|
||||
<TableHead>pO2</TableHead>
|
||||
<TableHead>HCO3</TableHead>
|
||||
<TableHead>BE</TableHead>
|
||||
<TableHead>SatO2</TableHead>
|
||||
<TableHead>Lactato</TableHead>
|
||||
<TableHead>FiO2</TableHead>
|
||||
<TableHead>PaFiO2</TableHead>
|
||||
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{abFiltered.map(a => (
|
||||
<TableRow key={a.id}>
|
||||
<TableCell>{a.fecha} {a.hora || ''}</TableCell>
|
||||
<TableCell>{a.ph}</TableCell>
|
||||
<TableCell>{a.pco2}</TableCell>
|
||||
<TableCell>{a.po2}</TableCell>
|
||||
<TableCell>{a.hco3}</TableCell>
|
||||
<TableCell>{a.be}</TableCell>
|
||||
<TableCell>{a.sato2}</TableCell>
|
||||
<TableCell>{a.lactato}</TableCell>
|
||||
<TableCell>{a.fio2}</TableCell>
|
||||
|
||||
|
||||
<TableCell>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button size="sm" variant="ghost" className="h-8 w-8 p-0">
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => loadEdit(a)}>
|
||||
<Edit className="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => del(a.id)} className="text-red-600">
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
Eliminar
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
}
|
||||
</div>
|
||||
);
|
||||
@@ -2162,4 +2219,126 @@ function SeccionInterconsultas({ interconsultas, pacienteId, internacionId, add,
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SeccionATB({ atb, internacionId, pacienteId, add, update, del }: {
|
||||
atb: ATB[];
|
||||
internacionId: string;
|
||||
pacienteId: string;
|
||||
add: (a: Omit<ATB, 'id'>) => void;
|
||||
update: (id: string, data: Partial<ATB>) => void;
|
||||
del: (id: string) => void;
|
||||
}) {
|
||||
const [dialog, setDialog] = useState(false);
|
||||
const [edit, setEdit] = useState<ATB | null>(null);
|
||||
const [antibiotico, setAntibiotico] = useState('');
|
||||
const [fechaInicio, setFechaInicio] = useState(new Date().toISOString().split('T')[0]);
|
||||
const [fechaFinalizacion, setFechaFinalizacion] = useState('');
|
||||
|
||||
const calcularDias = (inicio: string, fin?: string): number => {
|
||||
const ini = new Date(inicio);
|
||||
const finDate = fin ? new Date(fin) : new Date();
|
||||
const diff = finDate.getTime() - ini.getTime();
|
||||
return Math.ceil(diff / (1000 * 60 * 60 * 24));
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
setEdit(null);
|
||||
setAntibiotico('');
|
||||
setFechaInicio(new Date().toISOString().split('T')[0]);
|
||||
setFechaFinalizacion('');
|
||||
};
|
||||
|
||||
const loadEdit = (a: ATB) => {
|
||||
setEdit(a);
|
||||
setAntibiotico(a.antibiotico);
|
||||
setFechaInicio(a.fechaInicio);
|
||||
setFechaFinalizacion(a.fechaFinalizacion || '');
|
||||
};
|
||||
|
||||
const handleGuardar = () => {
|
||||
if (!antibiotico.trim() || !fechaInicio) return;
|
||||
if (edit) {
|
||||
update(edit.id, { antibiotico, fechaInicio, fechaFinalizacion: fechaFinalizacion || undefined });
|
||||
} else {
|
||||
add({ pacienteId, internacionId, antibiotico, fechaInicio, fechaFinalizacion: fechaFinalizacion || undefined });
|
||||
}
|
||||
setDialog(false);
|
||||
reset();
|
||||
};
|
||||
|
||||
const atbOrdenado = atb.sort((a, b) => new Date(b.fechaInicio).getTime() - new Date(a.fechaInicio).getTime());
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between">
|
||||
<Button variant="outline" onClick={() => { reset(); setDialog(true); }}>
|
||||
<Plus className="h-4 w-4 mr-2" />Nuevo
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Dialog open={dialog} onOpenChange={setDialog}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{edit ? 'Editar' : 'Nuevo'} Antibiótico</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label>Antibiótico</Label>
|
||||
<Input value={antibiotico} onChange={e => setAntibiotico(e.target.value)} placeholder="Nombre del antibiótico" />
|
||||
</div>
|
||||
<div>
|
||||
<Label>Fecha de Inicio</Label>
|
||||
<Input type="date" value={fechaInicio} onChange={e => setFechaInicio(e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
<Label>Fecha de Finalización (opcional)</Label>
|
||||
<Input type="date" value={fechaFinalizacion} onChange={e => setFechaFinalizacion(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2 mt-4">
|
||||
<Button variant="outline" onClick={() => setDialog(false)}>
|
||||
<X className="h-4 w-4 mr-2" />Cancelar
|
||||
</Button>
|
||||
<Button onClick={handleGuardar}>
|
||||
<Save className="h-4 w-4 mr-2" />Guardar
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{atbOrdenado.length === 0 ? (
|
||||
<div className="text-center py-8 text-gray-500">No hay antibiotics registrados</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{atbOrdenado.map(a => (
|
||||
<Card key={a.id}>
|
||||
<CardContent className="p-4 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="font-medium">{a.antibiotico}</div>
|
||||
<div className="text-sm text-gray-500 flex flex-wrap gap-2 mt-1">
|
||||
<span>Inicio: {formatDateDDMMYYYY(a.fechaInicio)}</span>
|
||||
{a.fechaFinalizacion && (
|
||||
<span>Fin: {formatDateDDMMYYYY(a.fechaFinalizacion)}</span>
|
||||
)}
|
||||
<Badge className="bg-green-100 text-green-800">
|
||||
{calcularDias(a.fechaInicio, a.fechaFinalizacion)} días
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
<Button size="sm" variant="outline" onClick={() => loadEdit(a)}>
|
||||
<Pencil className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" className="text-red-600" onClick={() => del(a.id)}>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user