Agregar estudios complementarios en HC

- Agregar tabla estudiosComplementarios en SQLite y API
- Agregar interface EstudioComplementario en tipos
- Agregar funciones en store para gestionar estudios
- Nuevo tab en HistoriaClinica con filtro por internacion/todos
- Fix tema dark mode (colores black en lugar de blue)
This commit is contained in:
2026-04-16 01:22:15 -03:00
parent 60c123fac6
commit 97adfd08b7
6 changed files with 274 additions and 54 deletions
+159 -7
View File
@@ -39,7 +39,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
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 } from '@/types';
import type { Laboratorio, AcidoBase, Cultivo, Paciente, ResultadoLaboratorio, Internacion, Evolucion, Cama, EstudioComplementario } from '@/types';
import { formatDateDDMMYYYY } from '@/lib/utils';
interface HistoriaClinicaProps {
@@ -51,6 +51,7 @@ interface HistoriaClinicaProps {
laboratorios: Laboratorio[];
acidosBase: AcidoBase[];
cultivos: Cultivo[];
estudiosComplementarios: EstudioComplementario[];
onAgregarEvolucion: (evolucion: Omit<Evolucion, 'id'>) => void;
onActualizarEvolucion: (id: string, datos: Partial<Evolucion>) => void;
onEliminarEvolucion: (id: string) => void;
@@ -62,6 +63,8 @@ interface HistoriaClinicaProps {
onAgregarCultivo: (cultivo: Omit<Cultivo, 'id'>) => void;
onActualizarCultivo: (id: string, datos: Partial<Cultivo>) => void;
onEliminarCultivo: (id: string) => void;
onAgregarEstudioComplementario: (estudio: Omit<EstudioComplementario, 'id'>) => void;
onEliminarEstudioComplementario: (id: string) => void;
onActualizarInternacion: (id: string, datos: Partial<Internacion>) => void;
onActualizarCama: (id: string, datos: Partial<Cama>) => void;
onVolver: () => void;
@@ -148,6 +151,7 @@ export function HistoriaClinica({
laboratorios,
acidosBase,
cultivos,
estudiosComplementarios,
onAgregarEvolucion,
onActualizarEvolucion,
onEliminarEvolucion,
@@ -159,6 +163,8 @@ export function HistoriaClinica({
onAgregarCultivo,
onActualizarCultivo,
onEliminarCultivo,
onAgregarEstudioComplementario,
onEliminarEstudioComplementario,
onActualizarInternacion,
onActualizarCama,
onVolver,
@@ -509,6 +515,12 @@ export function HistoriaClinica({
<span className="sm:hidden">Cult</span>
({cultivos.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>
<span className="sm:hidden">Est</span>
({estudiosComplementarios.length})
</TabsTrigger>
</TabsList>
<TabsContent value="laboratorios" className="mt-4">
@@ -526,6 +538,16 @@ export function HistoriaClinica({
<TabsContent value="cultivos" className="mt-4">
<SeccionCultivos cults={cultivos} patient={paciente} add={onAgregarCultivo} update={onActualizarCultivo} del={onEliminarCultivo} />
</TabsContent>
<TabsContent value="estudios" className="mt-4">
<SeccionEstudiosComplementarios
estudios={estudiosComplementarios}
internacionId={internacion.id}
pacienteId={paciente.id}
add={onAgregarEstudioComplementario}
del={onEliminarEstudioComplementario}
/>
</TabsContent>
</Tabs>
</div>
);
@@ -1011,10 +1033,13 @@ if (!esUnidadSiguiente) {
</DialogContent>
</Dialog>
{labs.length === 0 ? <div className="text-center py-8 text-gray-400"><FlaskConical className="h-12 w-12 mx-auto mb-2 opacity-50" /><p>No hay laboratorios</p></div> :
<div className="overflow-x-auto min-w-full">
<Table>
<TableHeader>
{labs.length === 0 ? (
<div className="text-center py-8 text-gray-400"><FlaskConical className="h-12 w-12 mx-auto mb-2 opacity-50" /><p>No hay laboratorios</p></div>
) : (
<div className="relative w-full overflow-auto grid grid-cols-1">
<Table>
<TableHeader>
<TableRow>
<TableHead>Fecha</TableHead>
<TableHead>Hto</TableHead>
@@ -1085,8 +1110,9 @@ if (!esUnidadSiguiente) {
))}
</TableBody>
</Table>
</div>
}
</div>
)}
</div>
);
}
@@ -1657,4 +1683,130 @@ const resetRes = () => {
}
</div>
);
}
function SeccionEstudiosComplementarios({ estudios, internacionId, pacienteId, add, del }: {
estudios: EstudioComplementario[];
internacionId: string;
pacienteId: string;
add: (e: Omit<EstudioComplementario, 'id'>) => void;
del: (id: string) => void;
}) {
const [dialog, setDialog] = useState(false);
const [filtro, setFiltro] = useState<'todos' | 'internacion'>('internacion');
const [fecha, setFecha] = useState(new Date().toISOString().split('T')[0]);
const [tipo, setTipo] = useState('');
const [resultado, setResultado] = useState('');
const estudiosFiltrados = filtro === 'internacion'
? estudios.filter(e => e.internacionId === internacionId)
: estudios;
const reset = () => {
setFecha(new Date().toISOString().split('T')[0]);
setTipo('');
setResultado('');
};
const handleGuardar = () => {
if (!tipo || !resultado) return;
add({
pacienteId,
internacionId,
fecha,
tipo,
resultado,
});
setDialog(false);
reset();
};
return (
<div className="space-y-4">
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<div className="flex gap-2">
<Button variant="outline" onClick={() => { reset(); setDialog(true); }}>
<Plus className="h-4 w-4 mr-2" />
Nuevo Estudio
</Button>
</div>
<Select value={filtro} onValueChange={(v: 'todos' | 'internacion') => setFiltro(v)}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Filtrar" />
</SelectTrigger>
<SelectContent>
<SelectItem value="internacion">Solo esta internación</SelectItem>
<SelectItem value="todos">Todos los estudios</SelectItem>
</SelectContent>
</Select>
</div>
<Dialog open={dialog} onOpenChange={setDialog}>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle>Nuevo Estudio Complementario</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div>
<Label>Fecha</Label>
<Input type="date" value={fecha} onChange={e => setFecha(e.target.value)} />
</div>
<div>
<Label>Tipo de Estudio</Label>
<Input
value={tipo}
onChange={e => setTipo(e.target.value)}
placeholder="Ej: Radiografía de tórax, ECG, Tomografía, etc."
/>
</div>
<div>
<Label>Resultado</Label>
<textarea
className="w-full p-2 border rounded-md text-sm min-h-[100px]"
value={resultado}
onChange={e => setResultado(e.target.value)}
placeholder="Ingrese el resultado del estudio..."
/>
</div>
</div>
<div className="flex justify-end gap-2 mt-4">
<Button variant="outline" onClick={() => setDialog(false)}>Cancelar</Button>
<Button onClick={handleGuardar} disabled={!tipo || !resultado}>
<Save className="h-4 w-4 mr-2" />
Guardar
</Button>
</div>
</DialogContent>
</Dialog>
{estudiosFiltrados.length === 0 ? (
<div className="text-center py-8 text-gray-400">
<ClipboardList className="h-12 w-12 mx-auto mb-2 opacity-50" />
<p>No hay estudios complementarios</p>
</div>
) : (
<div className="space-y-3">
{estudiosFiltrados.sort((a, b) => new Date(b.fecha).getTime() - new Date(a.fecha).getTime()).map(e => (
<Card key={e.id} className="hover:shadow-md transition-shadow">
<CardContent className="p-4">
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3">
<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
<Calendar className="h-4 w-4 text-gray-500" />
<span className="text-sm font-medium">{e.fecha}</span>
</div>
<h4 className="font-semibold text-gray-900 dark:text-white">{e.tipo}</h4>
<p className="text-sm text-gray-600 dark:text-gray-300 mt-1 whitespace-pre-wrap">{e.resultado}</p>
</div>
<Button size="sm" variant="outline" className="text-red-600 self-start" onClick={() => del(e.id)}>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</CardContent>
</Card>
))}
</div>
)}
</div>
);
}