Agregar sección de notas generales en Historia Clínica y mejoras en Cultivos
This commit is contained in:
+34
-13
@@ -172,34 +172,43 @@ export function Cultivos({
|
||||
Protocolo: {cultivo.protocolo}
|
||||
</p>
|
||||
)}
|
||||
{cultivo.observaciones && (
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300 bg-gray-50 dark:bg-gray-800 p-2 rounded">
|
||||
{cultivo.observaciones}
|
||||
|
||||
{cultivo.estado === 'NAF/Pendiente' && cultivo.observaciones && (
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300 bg-gray-50 dark:bg-gray-800 p-2.5 rounded border border-gray-200 dark:border-gray-700">
|
||||
<span className="font-semibold">Observaciones:</span> {cultivo.observaciones}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{cultivo.estado === 'Parcial' && cultivo.germen && (
|
||||
<div className="bg-orange-50 p-3 rounded-lg border border-orange-200">
|
||||
<p className="text-sm font-medium text-orange-800 flex items-center gap-2">
|
||||
{cultivo.estado === 'Parcial' && (
|
||||
<div className="bg-orange-50 dark:bg-orange-950/60 p-3 rounded-lg border border-orange-200 dark:border-orange-800/50 space-y-2">
|
||||
{cultivo.germen && (
|
||||
<p className="text-sm font-medium text-orange-800 dark:text-orange-300 flex items-center gap-2">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
PARCIAL - Germen: {cultivo.germen}
|
||||
</p>
|
||||
)}
|
||||
{(cultivo.sensible || cultivo.resistente) && (
|
||||
<div className="mt-2 space-y-1">
|
||||
<div className="space-y-1">
|
||||
{cultivo.sensible && (
|
||||
<p className="text-xs text-orange-700">Sensible: {cultivo.sensible}</p>
|
||||
<p className="text-xs text-orange-700 dark:text-orange-300">Sensible: {cultivo.sensible}</p>
|
||||
)}
|
||||
{cultivo.resistente && (
|
||||
<p className="text-xs text-orange-700">Resistente: {cultivo.resistente}</p>
|
||||
<p className="text-xs text-orange-700 dark:text-orange-300">Resistente: {cultivo.resistente}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{cultivo.observaciones && (
|
||||
<p className="text-xs text-orange-800 dark:text-orange-200 border-t border-orange-200 dark:border-orange-800/60 pt-2 mt-1">
|
||||
<span className="font-semibold">Observaciones:</span> {cultivo.observaciones}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{cultivo.estado === 'Positivo' && cultivo.germen && (
|
||||
<div className="flex flex-col gap-2.5 mt-2">
|
||||
{cultivo.estado === 'Positivo' && (
|
||||
<div className="flex flex-col gap-2 mt-2">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{cultivo.germen && (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="border-red-600 border bg-red-50 text-red-800 dark:bg-red-950/60 dark:text-red-300 dark:border-red-500 rounded px-2.5 py-1 font-medium text-sm inline-flex items-center gap-1.5"
|
||||
@@ -207,6 +216,7 @@ export function Cultivos({
|
||||
<AlertCircle className="h-4 w-4 text-red-600 shrink-0" />
|
||||
<span>Germen: {cultivo.germen} {cultivo.fechaResultado ? `(Definitivo: ${cultivo.fechaResultado})` : ''}</span>
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{cultivo.sensible && (
|
||||
<Badge
|
||||
@@ -226,15 +236,26 @@ export function Cultivos({
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{cultivo.observaciones && (
|
||||
<div className="bg-red-50/60 dark:bg-red-950/40 p-2.5 rounded-md border border-red-200/80 dark:border-red-900/50 text-xs text-red-900 dark:text-red-200">
|
||||
<span className="font-semibold">Observaciones:</span> {cultivo.observaciones}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{cultivo.estado === 'Negativo' && (
|
||||
<div className="bg-green-50 p-3 rounded-lg border border-green-200">
|
||||
<p className="text-sm font-medium text-green-800 flex items-center gap-2">
|
||||
<div className="bg-green-50 dark:bg-green-900/60 p-3 rounded-lg border border-green-200 dark:border-green-700 mt-2 space-y-2">
|
||||
<p className="text-sm font-medium text-green-800 dark:text-green-200 flex items-center gap-2">
|
||||
<CheckCircle2 className="h-4 w-4" />
|
||||
Cultivo Negativo
|
||||
</p>
|
||||
{cultivo.observaciones && (
|
||||
<p className="text-xs text-green-800 dark:text-green-200 border-t border-green-200 dark:border-green-700/80 pt-2 mt-1">
|
||||
<span className="font-semibold">Observaciones:</span> {cultivo.observaciones}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { toast } from 'sonner';
|
||||
import { User } from 'lucide-react';
|
||||
@@ -55,9 +55,8 @@ import { Label } from '@/components/ui/label';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, SelectGroup, SelectLabel } from '@/components/ui/select';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Tabs, TabsContent } from '@/components/ui/tabs';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||
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, OtroLaboratorio } from '@/types';
|
||||
@@ -203,6 +202,69 @@ function wrapText(text: string, font: { widthOfTextAtSize: (text: string, size:
|
||||
|
||||
|
||||
|
||||
function SeccionNotasPaciente({ paciente, canEdit }: { paciente: Paciente; canEdit?: boolean }) {
|
||||
const { actualizarPaciente } = useHospitalStore();
|
||||
const [textoNotas, setTextoNotas] = useState(paciente.notas || '');
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [guardado, setGuardado] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
setTextoNotas(paciente.notas || '');
|
||||
setGuardado(true);
|
||||
}, [paciente.id, paciente.notas]);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setTextoNotas(e.target.value);
|
||||
setGuardado(false);
|
||||
};
|
||||
|
||||
const handleGuardar = async () => {
|
||||
try {
|
||||
setIsSaving(true);
|
||||
await actualizarPaciente(paciente.id, { notas: textoNotas });
|
||||
toast.success('Notas del paciente guardadas correctamente');
|
||||
setGuardado(true);
|
||||
} catch (err) {
|
||||
console.error('Error al guardar notas:', err);
|
||||
toast.error('Error al guardar las notas del paciente');
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="border-t pt-3 space-y-3">
|
||||
<Textarea
|
||||
className="w-full p-3 text-sm min-h-[120px] bg-background text-foreground focus:ring-2 focus:ring-primary focus:outline-none disabled:opacity-60 disabled:cursor-not-allowed resize-y"
|
||||
placeholder="Escriba aquí las notas generales del paciente..."
|
||||
value={textoNotas}
|
||||
onChange={handleChange}
|
||||
disabled={!canEdit || isSaving}
|
||||
/>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div>
|
||||
{!guardado && (
|
||||
<span className="text-xs text-amber-600 dark:text-amber-400 font-medium">
|
||||
Cambios sin guardar
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{canEdit && (
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={handleGuardar}
|
||||
disabled={isSaving || guardado}
|
||||
className="flex items-center gap-1.5"
|
||||
>
|
||||
<Save className="h-4 w-4" />
|
||||
{isSaving ? 'Guardando...' : 'Guardar Notas'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function HistoriaClinica({
|
||||
internacion,
|
||||
paciente,
|
||||
@@ -389,11 +451,11 @@ export function HistoriaClinica({
|
||||
</>
|
||||
)}
|
||||
|
||||
<Accordion type="single" collapsible>
|
||||
<Accordion type="multiple" className="w-full space-y-1">
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>Detalle Episodio Actual</AccordionTrigger>
|
||||
<AccordionContent className="p4">
|
||||
<div className="border-t space-y-4">
|
||||
<AccordionContent>
|
||||
<div className="border-t space-y-4 pt-3">
|
||||
|
||||
|
||||
<div>
|
||||
@@ -527,6 +589,13 @@ export function HistoriaClinica({
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value="notas">
|
||||
<AccordionTrigger>Notas</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<SeccionNotasPaciente paciente={paciente} canEdit={canEdit} />
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
|
||||
</div>
|
||||
@@ -1315,7 +1384,7 @@ function SeccionLaboratorios({ lab, otrosLaboratorios = [], patientId, internaci
|
||||
}
|
||||
|
||||
// Clean parenthesized reference values/ranges if present (e.g. "(1.6 - 2.6 mg/dL)")
|
||||
let cleanSubLinea = subLinea.replace(/\([^)]*?(?:-|–|—|:|[0-9]\s*[-–—]\s*[0-9]|ref|vr|val|norm|min|max)[^)]*?\)/gi, ' ');
|
||||
const cleanSubLinea = subLinea.replace(/\([^)]*?(?:-|–|—|:|[0-9]\s*[-–—]\s*[0-9]|ref|vr|val|norm|min|max)[^)]*?\)/gi, ' ');
|
||||
|
||||
let match = cleanSubLinea.match(/(\d+(?:[.,]\d+)?)/);
|
||||
if (!match) {
|
||||
@@ -1763,10 +1832,10 @@ function SeccionLaboratorios({ lab, otrosLaboratorios = [], patientId, internaci
|
||||
<Badge variant="secondary" className="text-[10px] px-1.5 py-0.5">{item.source === 'lab' ? 'Obs. de Lab' : 'Otros'}</Badge>
|
||||
{canEdit && (
|
||||
<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 any)} 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 as OtroLaboratorio)} title="Editar">
|
||||
<Pencil className="h-3.5 w-3.5" />
|
||||
</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 any)} 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 as OtroLaboratorio)} title="Eliminar">
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -2672,12 +2741,14 @@ function SeccionCultivos({ cults, patient, internacionId, add, update, del, canE
|
||||
setGermen('');
|
||||
setSensible('');
|
||||
setResistente('');
|
||||
setObservaciones('');
|
||||
setSelected(null);
|
||||
};
|
||||
|
||||
const openParcial = (c: Cultivo) => {
|
||||
setSelected(c);
|
||||
setProtocolo(c.protocolo || '');
|
||||
setObservaciones(c.observaciones || '');
|
||||
setGermen(c.germen || '');
|
||||
setSensible(c.sensible || '');
|
||||
setResistente(c.resistente || '');
|
||||
@@ -2687,6 +2758,7 @@ function SeccionCultivos({ cults, patient, internacionId, add, update, del, canE
|
||||
const openDefinitivo = (c: Cultivo) => {
|
||||
setSelected(c);
|
||||
setProtocolo(c.protocolo || '');
|
||||
setObservaciones(c.observaciones || '');
|
||||
setFechaResultado(c.fechaResultado || getLocalToday());
|
||||
setEstadoResultado(c.estado === 'Parcial' ? 'Positivo' : c.estado);
|
||||
setGermen(c.germen || '');
|
||||
@@ -2725,6 +2797,7 @@ function SeccionCultivos({ cults, patient, internacionId, add, update, del, canE
|
||||
germen,
|
||||
sensible,
|
||||
resistente,
|
||||
observaciones: observaciones || undefined,
|
||||
fechaResultado: selected.fechaResultado || getLocalToday()
|
||||
});
|
||||
setResDialog(false);
|
||||
@@ -2733,7 +2806,15 @@ function SeccionCultivos({ cults, patient, internacionId, add, update, del, canE
|
||||
|
||||
const handleDefinitivo = () => {
|
||||
if (!selected) return;
|
||||
update(selected.id, { protocolo, fechaResultado, estado: estadoResultado, germen, sensible, resistente });
|
||||
update(selected.id, {
|
||||
protocolo,
|
||||
fechaResultado,
|
||||
estado: estadoResultado,
|
||||
germen,
|
||||
sensible,
|
||||
resistente,
|
||||
observaciones: observaciones || undefined
|
||||
});
|
||||
setEditDialog(false);
|
||||
resetRes();
|
||||
};
|
||||
@@ -2801,6 +2882,7 @@ function SeccionCultivos({ cults, patient, internacionId, add, update, del, canE
|
||||
<div><Label>Germen *</Label><Input value={germen} onChange={e => setGermen(e.target.value)} placeholder="Ej: Staphylococcos aureus" /></div>
|
||||
<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><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)} placeholder="Observaciones de la muestra o resultado parcial..." /></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 variant="outline" onClick={handleParcial} disabled={!germen}><Save className="h-4 w-4 mr-2" />Guardar Parcial</Button></div>
|
||||
</DialogContent>
|
||||
@@ -2830,6 +2912,7 @@ function SeccionCultivos({ cults, patient, internacionId, add, update, del, canE
|
||||
<div><Label>Resistente a:</Label><Input value={resistente} onChange={e => setResistente(e.target.value)} placeholder="Ej: Ampicilina, Cefazolina" /></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)} placeholder="Observaciones de la muestra o resultado definitivo..." /></div>
|
||||
</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>
|
||||
@@ -2877,20 +2960,34 @@ function SeccionCultivos({ cults, patient, internacionId, add, update, del, canE
|
||||
{c.estado === 'Parcial' ? 'Fecha Resultado Parcial' : 'Fecha Definitivo'}: {formatDateDDMMYYYY(c.fechaResultado)}
|
||||
</p>
|
||||
)}
|
||||
{c.observaciones && <p className="text-sm text-gray-600 dark:text-gray-300 bg-gray-50 dark:bg-gray-800 p-2 rounded">{c.observaciones}</p>}
|
||||
{c.estado === 'Parcial' && c.germen && (
|
||||
<div className="bg-orange-50 dark:bg-orange-950/60 p-3 rounded-lg border border-orange-200 dark:border-orange-800/50">
|
||||
<p className="text-sm font-medium text-orange-800 dark:text-orange-300 flex items-center gap-2"><AlertCircle className="h-4 w-4" />PARCIAL - Germen: {c.germen}</p>
|
||||
{c.estado === 'NAF/Pendiente' && c.observaciones && (
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300 bg-gray-50 dark:bg-gray-800 p-2.5 rounded border border-gray-200 dark:border-gray-700">
|
||||
<span className="font-semibold">Observaciones:</span> {c.observaciones}
|
||||
</p>
|
||||
)}
|
||||
{c.estado === 'Parcial' && (
|
||||
<div className="bg-orange-50 dark:bg-orange-950/60 p-3 rounded-lg border border-orange-200 dark:border-orange-800/50 space-y-2">
|
||||
{c.germen && (
|
||||
<p className="text-sm font-medium text-orange-800 dark:text-orange-300 flex items-center gap-2">
|
||||
<AlertCircle className="h-4 w-4" />PARCIAL - Germen: {c.germen}
|
||||
</p>
|
||||
)}
|
||||
{(c.sensible || c.resistente) && (
|
||||
<div className="mt-2 space-y-1">
|
||||
<div className="space-y-1">
|
||||
{c.sensible && <p className="text-xs text-orange-700 dark:text-orange-300">Sensible: {c.sensible}</p>}
|
||||
{c.resistente && <p className="text-xs text-orange-700 dark:text-orange-300">Resistente: {c.resistente}</p>}
|
||||
</div>
|
||||
)}
|
||||
{c.observaciones && (
|
||||
<p className="text-xs text-orange-800 dark:text-orange-200 border-t border-orange-200 dark:border-orange-800/60 pt-2 mt-1">
|
||||
<span className="font-semibold">Observaciones:</span> {c.observaciones}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{c.estado === 'Positivo' && c.germen && (
|
||||
{c.estado === 'Positivo' && (
|
||||
<div className="flex flex-col gap-2 mt-2">
|
||||
{c.germen && (
|
||||
<div>
|
||||
<Badge
|
||||
variant="outline"
|
||||
@@ -2900,6 +2997,7 @@ function SeccionCultivos({ cults, patient, internacionId, add, update, del, canE
|
||||
<span>Germen: {c.germen}</span>
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(c.sensible || c.resistente) && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
@@ -2922,11 +3020,22 @@ function SeccionCultivos({ cults, patient, internacionId, add, update, del, canE
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{c.observaciones && (
|
||||
<div className="bg-red-50/60 dark:bg-red-950/40 p-2.5 rounded-md border border-red-200/80 dark:border-red-900/50 text-xs text-red-900 dark:text-red-200">
|
||||
<span className="font-semibold">Observaciones:</span> {c.observaciones}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{c.estado === 'Negativo' && (
|
||||
<div className="bg-green-50 dark:bg-green-900 p-3 rounded-lg border border-green-200 dark:border-green-700 mt-2">
|
||||
<p className="text-sm font-medium text-green-800 flex items-center gap-2"><CheckCircle2 className="h-4 w-4" />Sin crecimiento de microorganismos</p>
|
||||
<div className="bg-green-50 dark:bg-green-900/60 p-3 rounded-lg border border-green-200 dark:border-green-700 mt-2 space-y-2">
|
||||
<p className="text-sm font-medium text-green-800 dark:text-green-200 flex items-center gap-2"><CheckCircle2 className="h-4 w-4" />Sin crecimiento de microorganismos</p>
|
||||
{c.observaciones && (
|
||||
<p className="text-xs text-green-800 dark:text-green-200 border-t border-green-200 dark:border-green-700/80 pt-2 mt-1">
|
||||
<span className="font-semibold">Observaciones:</span> {c.observaciones}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface Paciente {
|
||||
nacionalidad?: string;
|
||||
medicacionHabitual?: string;
|
||||
historiaClinica?: string;
|
||||
notas?: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user