Mejorar tema oscuro, añadir dropdownmenu en laboratorios, unificar estilos de botones a outline

This commit is contained in:
2026-04-15 18:35:40 -03:00
parent 83e4ad1c36
commit 4573e6e3d8
9 changed files with 207 additions and 123 deletions
+126 -24
View File
@@ -22,7 +22,11 @@ import {
Wind,
Droplets,
Save,
X
X,
MoreHorizontal,
Edit,
ClipboardList,
TrendingUp
} from 'lucide-react';
import { Card, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
@@ -33,6 +37,8 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
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 { formatDateDDMMYYYY } from '@/lib/utils';
@@ -212,7 +218,7 @@ export function HistoriaClinica({
<div className="flex items-center gap-4 mb-4">
<div>
<h1 className="text-2xl font-bold text-gray-900">Historia Clínica</h1>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white flex items-center gap-2"><ClipboardList className="h-6 w-6" />Historia Clínica</h1>
<p className="text-gray-500">
Cama: {cama?.numero || 'N/A'} | {paciente.apellido}, {paciente.nombre} | {paciente.fechaNacimiento ? calcularEdad(paciente.fechaNacimiento) + ' años' : ''} | DNI: {paciente.dni}
</p>
@@ -534,6 +540,8 @@ function SeccionLaboratorios({ lab, patientId, add, update, del }: {
}) {
const [dialog, setDialog] = useState(false);
const [obsDialog, setObsDialog] = useState(false);
const [evolDialog, setEvolDialog] = useState(false);
const [selectedLab, setSelectedLab] = useState<Laboratorio | null>(null);
const [edit, setEdit] = useState<Laboratorio | null>(null);
const [obsLab, setObsLab] = useState<Laboratorio | null>(null);
const [fecha, setFecha] = useState(new Date().toISOString().split('T')[0]);
@@ -556,6 +564,23 @@ function SeccionLaboratorios({ lab, patientId, add, update, del }: {
const [tp, setTp] = useState('');
const [kptt, setKptt] = useState('');
const [rin, setRin] = useState('');
const [parametroEvolucion, setParametroEvolucion] = useState('');
const parametrosLaboratorio = [
'Hematocrito', 'Hemoglobina', 'Leucocitos', 'Plaquetas',
'Glucemia', 'Urea', 'Creatinina',
'Sodio', 'Potasio', 'Cloro',
'Bilirrubina Total', 'Bilirrubina Directa', 'GOT', 'GPT',
'Tiempo de Protrombina', 'KPTT', 'INR'
];
const datosEvolucion = lab
.filter(l => l.resultados.some(r => r.parametro === parametroEvolucion))
.sort((a, b) => new Date(a.fecha).getTime() - new Date(b.fecha).getTime())
.map(l => {
const r = l.resultados.find(r => r.parametro === parametroEvolucion);
return { fecha: l.fecha, valor: r ? parseFloat(String(r.valor)) : null };
});
const getEstadoColor = (estado: ResultadoLaboratorio['estado']) => {
switch (estado) {
@@ -657,8 +682,11 @@ function SeccionLaboratorios({ lab, patientId, add, update, del }: {
return (
<div className="space-y-4">
<div className="flex justify-end">
<Button onClick={() => { reset(); setDialog(true); }}><Plus className="h-4 w-4 mr-2" />Nuevo Laboratorio</Button>
<div className="flex justify-between">
<div className="flex gap-2">
<Button variant="outline" onClick={() => { reset(); setDialog(true); }}><Plus className="h-4 w-4 mr-2" />Nuevo Laboratorio</Button>
<Button variant="outline" onClick={() => { setSelectedLab(lab[0] || null); setParametroEvolucion('Hematocrito'); setEvolDialog(true); }}><TrendingUp className="h-4 w-4 mr-2" />Evolución</Button>
</div>
</div>
<Dialog open={dialog} onOpenChange={setDialog}>
<DialogContent className="sm:max-w-2xl max-h-[90vh] overflow-y-auto">
@@ -725,7 +753,7 @@ function SeccionLaboratorios({ lab, patientId, add, update, del }: {
<div className="space-y-2">
<p className="text-sm text-gray-500">{obsLab?.fecha}</p>
<p className="text-sm text-gray-500">{obsLab?.tipo}</p>
<div className="bg-gray-50 p-3 rounded-lg border border-gray-200">
<div className="bg-gray-50 p-3 rounded-lg border border-gray-200 dark:bg-gray-800 dark:border-gray-700">
<p className="whitespace-pre-wrap">{obsLab?.observaciones}</p>
</div>
</div>
@@ -733,6 +761,64 @@ function SeccionLaboratorios({ lab, patientId, add, update, del }: {
</DialogContent>
</Dialog>
<Dialog open={evolDialog} onOpenChange={setEvolDialog}>
<DialogContent className="sm:max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<TrendingUp className="h-5 w-5" />
Evolución de Laboratorio
</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div>
<Label>Parámetro a visualizar</Label>
<Select value={parametroEvolucion} onValueChange={setParametroEvolucion}>
<SelectTrigger><SelectValue placeholder="Seleccionar parámetro" /></SelectTrigger>
<SelectContent>
{parametrosLaboratorio.map(p => (
<SelectItem key={p} value={p}>{p}</SelectItem>
))}
</SelectContent>
</Select>
</div>
{parametroEvolucion && datosEvolucion.length > 0 && (
<div className="h-64 bg-muted/30 rounded-lg p-4">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={datosEvolucion}>
<CartesianGrid strokeDasharray="3 3" className="opacity-30" />
<XAxis dataKey="fecha" tick={{ fontSize: 12 }} />
<YAxis tick={{ fontSize: 12 }} domain={['auto', 'auto']} />
<Tooltip
contentStyle={{
backgroundColor: 'hsl(var(--card))',
border: '1px solid hsl(var(--border))',
borderRadius: '8px'
}}
/>
<Line
type="monotone"
dataKey="valor"
stroke="hsl(var(--primary))"
strokeWidth={2}
dot={{ fill: 'hsl(var(--primary))', r: 4 }}
/>
</LineChart>
</ResponsiveContainer>
</div>
)}
{parametroEvolucion && datosEvolucion.length === 0 && (
<div className="text-center py-8 text-gray-400">
<TrendingUp className="h-12 w-12 mx-auto mb-2 opacity-50" />
<p>No hay datos para este parámetro</p>
</div>
)}
</div>
<div className="flex justify-end gap-2 mt-4">
<Button onClick={() => setEvolDialog(false)}>Cerrar</Button>
</div>
</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">
<Table>
@@ -756,7 +842,7 @@ function SeccionLaboratorios({ lab, patientId, add, update, del }: {
<TableHead>TP</TableHead>
<TableHead>KPTT</TableHead>
<TableHead>RIN</TableHead>
<TableHead></TableHead>
<TableHead>Acciones</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -781,11 +867,27 @@ function SeccionLaboratorios({ lab, patientId, add, update, del }: {
<TableCell className={getValorStyle(l, 'KPTT')}>{getValor(l, 'KPTT')}</TableCell>
<TableCell className={getValorStyle(l, 'INR')}>{getValor(l, 'INR')}</TableCell>
<TableCell>
<div className="flex items-center gap-1">
{l.observaciones && <Button size="sm" variant="ghost" onClick={() => { setObsLab(l); setObsDialog(true); }}><ChevronRight className="h-4 w-4" /></Button>}
<Button size="sm" variant="ghost" onClick={() => loadEdit(l)}><Pencil className="h-4 w-4" /></Button>
<Button size="sm" variant="ghost" className="text-red-600" onClick={() => del(l.id)}><Trash2 className="h-4 w-4" /></Button>
</div>
<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(l)}>
<Edit className="mr-2 h-4 w-4" />
Editar
</DropdownMenuItem>
<DropdownMenuItem onClick={() => del(l.id)} className="text-red-600">
<Trash2 className="mr-2 h-4 w-4" />
Eliminar
</DropdownMenuItem>
<DropdownMenuItem onClick={() => { setObsLab(l); setObsDialog(true); }}>
<ClipboardList className="mr-2 h-4 w-4" />
Ver Observaciones
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
@@ -893,8 +995,8 @@ function SeccionEvoluciones({ evos, internacionId, add, update, del }: {
return (
<div className="space-y-4">
<div className="flex justify-end">
<Button onClick={() => { reset(); setDialog(true); }}><Plus className="h-4 w-4 mr-2" />Nueva Evolución</Button>
<div className="flex">
<Button variant="outline" onClick={() => { reset(); setDialog(true); }}><Plus className="h-4 w-4 mr-2" />Nueva Evolución</Button>
</div>
<Dialog open={dialog} onOpenChange={setDialog}>
<DialogContent className="sm:max-w-3xl max-h-[90vh] overflow-y-auto">
@@ -1037,8 +1139,8 @@ function SeccionAcidosBase({ ab, patientId, add, del }: {
return (
<div className="space-y-4">
<div className="flex justify-end">
<Button onClick={() => { reset(); setDialog(true); }}><Plus className="h-4 w-4 mr-2" />Nueva Gasometría</Button>
<div className="flex">
<Button variant="outline" onClick={() => { reset(); setDialog(true); }}><Plus className="h-4 w-4 mr-2" />Nueva Gasometría</Button>
</div>
<Dialog open={dialog} onOpenChange={setDialog}>
<DialogContent className="sm:max-w-2xl max-h-[90vh] overflow-y-auto">
@@ -1212,8 +1314,8 @@ const resetRes = () => {
return (
<div className="space-y-4">
<div className="flex justify-end">
<Button onClick={() => { reset(); setDialog(true); }}><Plus className="h-4 w-4 mr-2" />Nuevo Cultivo</Button>
<div className="flex">
<Button variant="outline" onClick={() => { reset(); setDialog(true); }}><Plus className="h-4 w-4 mr-2" />Nuevo Cultivo</Button>
</div>
<Dialog open={dialog} onOpenChange={setDialog}>
<DialogContent className="sm:max-w-lg">
@@ -1242,7 +1344,7 @@ const resetRes = () => {
</div>
<div><Label>Observaciones</Label><textarea className="w-full p-2 border rounded-md text-sm min-h-[60px]" value={observaciones} onChange={e => setObservaciones(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={handle}><Plus className="h-4 w-4 mr-2" />Guardar</Button></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 variant="outline" onClick={handle}><Plus className="h-4 w-4 mr-2" />Guardar</Button></div>
</DialogContent>
</Dialog>
@@ -1259,7 +1361,7 @@ const resetRes = () => {
<div><Label>Sensible a:</Label><Input value={sensible} onChange={e => setSensible(e.target.value)} placeholder="Ej: Amikacina, Vancomicina" /></div>
<div><Label>Resistente a:</Label><Input value={resistente} onChange={e => setResistente(e.target.value)} placeholder="Ej: Oxacilina" /></div>
</div>
<div className="flex justify-end gap-2 mt-4"><Button variant="outline" onClick={() => { resetRes(); setResDialog(false); }}><X className="h-4 w-4 mr-2" />Cancelar</Button><Button onClick={handleParcial} disabled={!germen}><Save className="h-4 w-4 mr-2" />Guardar Parcial</Button></div>
<div className="flex justify-end gap-2 mt-4"><Button variant="outline" onClick={() => { resetRes(); setResDialog(false); }}><X className="h-4 w-4 mr-2" />Cancelar</Button><Button variant="outline" onClick={handleParcial} disabled={!germen}><Save className="h-4 w-4 mr-2" />Guardar Parcial</Button></div>
</DialogContent>
</Dialog>
@@ -1288,7 +1390,7 @@ const resetRes = () => {
</>
)}
</div>
<div className="flex justify-end gap-2 mt-4"><Button variant="outline" onClick={() => { resetRes(); setEditDialog(false); }}><X className="h-4 w-4 mr-2" />Cancelar</Button><Button onClick={handleDefinitivo} disabled={estadoResultado === 'Positivo' && !germen}><Save className="h-4 w-4 mr-2" />Guardar Definitivo</Button></div>
<div className="flex justify-end gap-2 mt-4"><Button variant="outline" onClick={() => { resetRes(); setEditDialog(false); }}><X className="h-4 w-4 mr-2" />Cancelar</Button><Button variant="outline" onClick={handleDefinitivo} disabled={estadoResultado === 'Positivo' && !germen}><Save className="h-4 w-4 mr-2" />Guardar Definitivo</Button></div>
</DialogContent>
</Dialog>
@@ -1312,18 +1414,18 @@ const resetRes = () => {
<div className="flex items-center gap-2">
{c.estado === 'NAF/Pendiente' && (
<>
<Button size="sm" onClick={() => openParcial(c)}><AlertCircle className="h-4 w-4 mr-1" />Parcial</Button>
<Button size="sm" variant="default" onClick={() => { openDefinitivo(c); setIsParcialMode(false); }}><CheckCircle2 className="h-4 w-4 mr-1" />Definitivo</Button>
<Button size="sm" variant="outline" onClick={() => openParcial(c)}><AlertCircle className="h-4 w-4 mr-1" />Parcial</Button>
<Button size="sm" variant="outline" onClick={() => { openDefinitivo(c); setIsParcialMode(false); }}><CheckCircle2 className="h-4 w-4 mr-1" />Definitivo</Button>
</>
)}
{c.estado === 'Parcial' && (
<>
<Button size="sm" variant="outline" onClick={() => openParcial(c)}><Pencil className="h-4 w-4 mr-1" />Editar Parcial</Button>
<Button size="sm" variant="default" onClick={() => openDefinitivo(c)}><CheckCircle2 className="h-4 w-4 mr-1" />Definitivo</Button>
<Button size="sm" variant="outline" onClick={() => openDefinitivo(c)}><CheckCircle2 className="h-4 w-4 mr-1" />Definitivo</Button>
</>
)}
{(c.estado === 'Positivo' || c.estado === 'Negativo') && <Button size="sm" variant="outline" onClick={() => openDefinitivo(c)}><Pencil className="h-4 w-4" /></Button>}
<Button size="sm" variant="ghost" className="text-red-600" onClick={() => del(c.id)}><Trash2 /></Button>
<Button size="sm" variant="outline" className="text-red-600" onClick={() => del(c.id)}><Trash2 /></Button>
</div>
</div>
{c.protocolo && <p className="text-xs text-gray-500">Protocolo: {c.protocolo}</p>}