564 lines
29 KiB
TypeScript
564 lines
29 KiB
TypeScript
import { useState } from 'react';
|
|
import { createPortal } from 'react-dom';
|
|
import { toast } from 'sonner';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Card, CardContent } from '@/components/ui/card';
|
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
|
import { Plus, Pencil, Trash2, X } from 'lucide-react';
|
|
import type { Indicacion, IndicacionTipo, ViaAdministracion, TipoPlanHidratacion, TipoInsulina, ATB } from '@/types';
|
|
import { useHospitalStore } from '@/hooks/useHospitalStore';
|
|
import { getNombreProfesional, getLocalToday } from '@/lib/utils';
|
|
|
|
export function SeccionIndicaciones({ recomendaciones, internacionId, pacienteId, add, update, del, movimientos, onAgregarMovimiento, canEdit, atbList = [], addATB, updateATB, portalNode}: {
|
|
recomendaciones: Indicacion[];
|
|
internacionId: string;
|
|
pacienteId?: string;
|
|
add: (i: Omit<Indicacion, 'id'>) => Promise<unknown>;
|
|
update: (id: string, datos: Partial<Indicacion>) => void;
|
|
del: (id: string) => void;
|
|
movimientos?: unknown[];
|
|
onAgregarMovimiento?: (m: unknown) => void;
|
|
canEdit?: boolean;
|
|
portalNode?: HTMLDivElement | null;
|
|
atbList?: ATB[];
|
|
addATB?: (a: Omit<ATB, 'id'>) => Promise<unknown>;
|
|
updateATB?: (id: string, datos: Partial<ATB>) => void;
|
|
}) {
|
|
const { currentUser } = useHospitalStore();
|
|
const list: Indicacion[] = Array.isArray(recomendaciones) ? recomendaciones : [];
|
|
const movs = Array.isArray(movimientos) ? movimientos : [];
|
|
const [dialog, setDialog] = useState(false);
|
|
const [edit, setEdit] = useState<Indicacion | null>(null);
|
|
const [deleteConfirmInd, setDeleteConfirmInd] = useState<Indicacion | null>(null);
|
|
const effectiveDeleteMedico = getNombreProfesional(currentUser);
|
|
|
|
const formatIndicacion = (i: Indicacion): string => {
|
|
if (i.tipo === 'Farmacologica' || i.tipo === 'Farmacologica Profilactica' || i.tipo === 'Farmacologica Antibiótico') return `${i.droga || ''} ${i.dosis || ''} ${i.frecuenciaHoras ? `c/${i.frecuenciaHoras}hs` : ''} ${i.via || ''}`.trim();
|
|
if (i.tipo === 'Farmacologica Insulina') return `${i.tipoInsulina || ''}${i.unidadesDesayuno ? ` - PreDesayuno: ${i.unidadesDesayuno}U` : ''}${i.unidadesAlmuerzo ? ` - PreAlmuerzo: ${i.unidadesAlmuerzo}U` : ''}${i.unidadesNoche ? ` - 23hs: ${i.unidadesNoche}U` : ''}`.trim();
|
|
if (i.tipo === 'No Farmacologica') return i.indicacionNoFco || '';
|
|
if (i.tipo === 'PHP' || i.tipo === 'PHP Paralelo') return `${i.tipoPlan || ''} ${i.cantidadMl || ''}ml en ${i.tiempoHoras || ''}hs`;
|
|
if (i.tipo === 'PHP Alterno') return `${i.tipoPlan || ''} ${i.cantidadMl || ''}ml + ${i.tipoPlan2 || ''} ${i.cantidadMl2 || ''}ml en ${i.tiempoHoras || ''}hs`;
|
|
return '';
|
|
};
|
|
const [tipo, setTipo] = useState<IndicacionTipo>('Farmacologica');
|
|
const [sortField, setSortField] = useState<'fecha' | 'profesional' | 'tipo'>('fecha');
|
|
const [sortDir, setSortDir] = useState<'asc' | 'desc'>('desc');
|
|
|
|
const handleSort = (field: 'fecha' | 'profesional' | 'tipo') => {
|
|
if (sortField === field) {
|
|
setSortDir(sortDir === 'asc' ? 'desc' : 'asc');
|
|
} else {
|
|
setSortField(field);
|
|
setSortDir('desc');
|
|
}
|
|
};
|
|
|
|
const sortedMovs = [...movs].sort((a, b) => {
|
|
const recordA = a as Record<string, unknown>;
|
|
const recordB = b as Record<string, unknown>;
|
|
const aVal = String(recordA[sortField] || '');
|
|
const bVal = String(recordB[sortField] || '');
|
|
if (sortField === 'fecha') {
|
|
const timeA = new Date(aVal.replace(' ', 'T')).getTime();
|
|
const timeB = new Date(bVal.replace(' ', 'T')).getTime();
|
|
return sortDir === 'asc' ? timeA - timeB : timeB - timeA;
|
|
}
|
|
return sortDir === 'asc' ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal);
|
|
});
|
|
const [droga, setDroga] = useState('');
|
|
const [dosis, setDosis] = useState('');
|
|
const [frecuenciaHoras, setFrecuenciaHoras] = useState<number | ''>('');
|
|
const [via, setVia] = useState<ViaAdministracion>('EV');
|
|
const [indicacionNoFco, setIndicacionNoFco] = useState('');
|
|
const [tipoPlan, setTipoPlan] = useState<TipoPlanHidratacion>('SF 0.9%');
|
|
const [tipoPlan2, setTipoPlan2] = useState<TipoPlanHidratacion>('Ringer Lactato');
|
|
const [cantidadMl, setCantidadMl] = useState<number | ''>('');
|
|
const [cantidadMl2, setCantidadMl2] = useState<number | ''>('');
|
|
const [tiempoHoras, setTiempoHoras] = useState<number | ''>(24);
|
|
const [tipoInsulina, setTipoInsulina] = useState<TipoInsulina>('NPH');
|
|
const [unidadesDesayuno, setUnidadesDesayuno] = useState<number | ''>('');
|
|
const [unidadesAlmuerzo, setUnidadesAlmuerzo] = useState<number | ''>('');
|
|
const [unidadesNoche, setUnidadesNoche] = useState<number | ''>('');
|
|
const effectiveMedico = getNombreProfesional(currentUser);
|
|
const [showHistorial, setShowHistorial] = useState(false);
|
|
|
|
const vias: ViaAdministracion[] = ['Via Oral', 'EV', 'IM', 'SC', 'Por GGT', 'Por SNG'];
|
|
const planes: TipoPlanHidratacion[] = ['SF 0.9%', 'Dextrosa 5%', 'Dextrosa 10%', 'Dextrosa 25%', 'Ringer Lactato'];
|
|
const tiposInsulina: TipoInsulina[] = ['NPH', 'Glargina'];
|
|
const tipos: IndicacionTipo[] = ['Farmacologica', 'Farmacologica Antibiótico', 'Farmacologica Profilactica', 'Farmacologica Insulina', 'No Farmacologica', 'PHP', 'PHP Paralelo', 'PHP Alterno'];
|
|
|
|
const reset = () => {
|
|
setEdit(null);
|
|
setTipo('Farmacologica');
|
|
setDroga('');
|
|
setDosis('');
|
|
setFrecuenciaHoras('');
|
|
setVia('EV');
|
|
setIndicacionNoFco('');
|
|
setTipoPlan('SF 0.9%');
|
|
setTipoPlan2('Ringer Lactato');
|
|
setCantidadMl('');
|
|
setCantidadMl2('');
|
|
setTiempoHoras(24);
|
|
setTipoInsulina('NPH');
|
|
setUnidadesDesayuno('');
|
|
setUnidadesAlmuerzo('');
|
|
setUnidadesNoche('');
|
|
};
|
|
|
|
const loadEdit = (i: Indicacion) => {
|
|
setEdit(i);
|
|
setTipo(i.tipo);
|
|
setDroga(i.droga || '');
|
|
setDosis(i.dosis || '');
|
|
setFrecuenciaHoras(i.frecuenciaHoras || '');
|
|
setVia(i.via || 'EV');
|
|
setIndicacionNoFco(i.indicacionNoFco || '');
|
|
setTipoPlan(i.tipoPlan || 'SF 0.9%');
|
|
setTipoPlan2(i.tipoPlan2 || 'Ringer Lactato');
|
|
setCantidadMl(i.cantidadMl || '');
|
|
setCantidadMl2(i.cantidadMl2 || '');
|
|
setTiempoHoras(i.tiempoHoras || 24);
|
|
setTipoInsulina(i.tipoInsulina || 'NPH');
|
|
setUnidadesDesayuno(i.unidadesDesayuno || '');
|
|
setUnidadesAlmuerzo(i.unidadesAlmuerzo || '');
|
|
setUnidadesNoche(i.unidadesNoche || '');
|
|
setDialog(true);
|
|
};
|
|
|
|
const handleGuardar = async () => {
|
|
if (!effectiveMedico.trim()) return;
|
|
if ((tipo === 'Farmacologica' || tipo === 'Farmacologica Profilactica' || tipo === 'Farmacologica Antibiótico') && (!droga.trim() || !dosis.trim())) return;
|
|
if (tipo === 'Farmacologica Insulina' && (!unidadesDesayuno && !unidadesAlmuerzo && !unidadesNoche)) return;
|
|
if (tipo === 'No Farmacologica' && !indicacionNoFco.trim()) return;
|
|
if ((tipo === 'PHP' || tipo === 'PHP Paralelo') && !cantidadMl) return;
|
|
if (tipo === 'PHP Alterno' && (!cantidadMl || !cantidadMl2)) return;
|
|
|
|
const data: Partial<Indicacion> = {
|
|
internacionId,
|
|
tipo,
|
|
estado: 'Activa',
|
|
medicoCrea: effectiveMedico,
|
|
fechaCrea: getLocalToday(),
|
|
};
|
|
|
|
if (tipo === 'Farmacologica' || tipo === 'Farmacologica Profilactica' || tipo === 'Farmacologica Antibiótico') {
|
|
data.droga = droga;
|
|
data.dosis = dosis;
|
|
data.frecuenciaHoras = frecuenciaHoras || undefined;
|
|
data.via = via;
|
|
} else if (tipo === 'Farmacologica Insulina') {
|
|
data.tipoInsulina = tipoInsulina;
|
|
data.unidadesDesayuno = unidadesDesayuno || undefined;
|
|
data.unidadesAlmuerzo = unidadesAlmuerzo || undefined;
|
|
data.unidadesNoche = unidadesNoche || undefined;
|
|
} else if (tipo === 'No Farmacologica') {
|
|
data.indicacionNoFco = indicacionNoFco;
|
|
} else if (tipo === 'PHP' || tipo === 'PHP Paralelo') {
|
|
data.tipoPlan = tipoPlan;
|
|
data.cantidadMl = cantidadMl;
|
|
data.tiempoHoras = tiempoHoras;
|
|
} else if (tipo === 'PHP Alterno') {
|
|
data.tipoPlan = tipoPlan;
|
|
data.cantidadMl = cantidadMl;
|
|
data.tipoPlan2 = tipoPlan2;
|
|
data.cantidadMl2 = cantidadMl2;
|
|
data.tiempoHoras = tiempoHoras;
|
|
}
|
|
|
|
const now = new Date();
|
|
const fecha = getLocalToday() + ' ' + now.toTimeString().split(' ')[0].substring(0, 5);
|
|
if (edit) {
|
|
if (edit.tipo === 'Farmacologica Antibiótico' && updateATB && addATB && atbList && pacienteId) {
|
|
const oldDrug = edit.droga || '';
|
|
const newDrug = droga || '';
|
|
if (oldDrug.toLowerCase() !== newDrug.toLowerCase()) {
|
|
const activeATB = atbList.find(a =>
|
|
a.internacionId === internacionId &&
|
|
a.antibiotico.toLowerCase() === oldDrug.toLowerCase() &&
|
|
!a.fechaFinalizacion
|
|
);
|
|
if (activeATB) {
|
|
const todayStr = getLocalToday();
|
|
try {
|
|
await updateATB(activeATB.id, { fechaFinalizacion: todayStr });
|
|
await addATB({
|
|
pacienteId,
|
|
internacionId,
|
|
antibiotico: newDrug,
|
|
fechaInicio: todayStr,
|
|
});
|
|
} catch (atbErr) {
|
|
console.error('Error al actualizar ATB en modificación de indicación:', atbErr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (onAgregarMovimiento) {
|
|
await onAgregarMovimiento({
|
|
indicacionId: edit.id,
|
|
internacionId,
|
|
tipo: 'Modificacion',
|
|
fecha,
|
|
profesional: effectiveMedico,
|
|
indicacionPrevia: formatIndicacion(edit),
|
|
indicacionNueva: formatIndicacion({ ...edit, ...data } as Indicacion),
|
|
});
|
|
}
|
|
await update(edit.id, data);
|
|
} else {
|
|
const newId = await add(data);
|
|
if (tipo === 'Farmacologica Antibiótico' && addATB && pacienteId) {
|
|
try {
|
|
await addATB({
|
|
pacienteId,
|
|
internacionId,
|
|
antibiotico: droga,
|
|
fechaInicio: data.fechaCrea,
|
|
});
|
|
} catch (atbErr) {
|
|
console.error('Error al agregar ATB automáticamente:', atbErr);
|
|
}
|
|
}
|
|
if (onAgregarMovimiento) {
|
|
await onAgregarMovimiento({
|
|
indicacionId: typeof newId === 'string' ? newId : '',
|
|
internacionId,
|
|
tipo: 'Nueva',
|
|
fecha,
|
|
profesional: effectiveMedico,
|
|
indicacionNueva: formatIndicacion({ ...data, id: newId } as Indicacion),
|
|
});
|
|
}
|
|
}
|
|
setDialog(false);
|
|
reset();
|
|
};
|
|
|
|
const handleSuspender = async (i: Indicacion, suspendioMedico: string) => {
|
|
const now = new Date();
|
|
const fecha = getLocalToday() + ' ' + now.toTimeString().split(' ')[0].substring(0, 5);
|
|
try {
|
|
if (onAgregarMovimiento) {
|
|
await onAgregarMovimiento({
|
|
indicacionId: i.id,
|
|
internacionId,
|
|
tipo: 'Suspencion',
|
|
fecha,
|
|
profesional: suspendioMedico,
|
|
indicacionPrevia: formatIndicacion(i),
|
|
});
|
|
}
|
|
|
|
if (i.tipo === 'Farmacologica Antibiótico' && updateATB && atbList) {
|
|
const fechaFin = getLocalToday();
|
|
const activeATB = atbList.find(a =>
|
|
a.internacionId === internacionId &&
|
|
a.antibiotico.toLowerCase() === (i.droga || '').toLowerCase() &&
|
|
!a.fechaFinalizacion
|
|
);
|
|
if (activeATB) {
|
|
try {
|
|
await updateATB(activeATB.id, { fechaFinalizacion: fechaFin });
|
|
} catch (atbErr) {
|
|
console.error('Error al finalizar ATB automáticamente:', atbErr);
|
|
}
|
|
}
|
|
}
|
|
|
|
await del(i.id);
|
|
toast.success('Indicación eliminada correctamente');
|
|
} catch (err) {
|
|
console.error('Error al eliminar indicación:', err);
|
|
toast.error('Error al eliminar la indicación');
|
|
}
|
|
};
|
|
|
|
const indsFilter = list.filter(ind => ind.internacionId === internacionId);
|
|
const activas = indsFilter.filter(ind => ind.estado === 'Activa');
|
|
|
|
return (
|
|
<div className="space-y-2.5">
|
|
{portalNode ? createPortal(
|
|
<>
|
|
{canEdit && <Button size="sm" variant="outline" onClick={() => { reset(); setDialog(true); }}>
|
|
<Plus className="h-4 w-4 mr-1.5" />Nueva Indicación
|
|
</Button>}
|
|
<Button size="sm" variant="ghost" onClick={() => setShowHistorial(!showHistorial)}>
|
|
{showHistorial ? 'Ocultar Historial' : `Ver Historial (${movs.length})`}
|
|
</Button>
|
|
</>,
|
|
portalNode
|
|
) : (
|
|
<div className="flex justify-between items-center mb-4">
|
|
{canEdit && <Button size="sm" variant="outline" onClick={() => { reset(); setDialog(true); }}>
|
|
<Plus className="h-4 w-4 mr-1.5" />Nueva Indicación
|
|
</Button>}
|
|
<Button size="sm" variant="ghost" onClick={() => setShowHistorial(!showHistorial)}>
|
|
{showHistorial ? 'Ocultar Historial' : `Ver Historial (${movs.length})`}
|
|
</Button>
|
|
</div>
|
|
)}
|
|
|
|
<Dialog open={dialog} onOpenChange={setDialog}>
|
|
<DialogContent className="sm:max-w-lg">
|
|
<DialogHeader>
|
|
<DialogTitle>{edit ? 'Editar' : 'Nueva'} Indicación</DialogTitle>
|
|
</DialogHeader>
|
|
<div className="space-y-4">
|
|
<div>
|
|
<Label>Tipo de Indicación</Label>
|
|
<Select value={tipo} onValueChange={(v: IndicacionTipo) => setTipo(v)}>
|
|
<SelectTrigger><SelectValue placeholder="Seleccionar tipo" /></SelectTrigger>
|
|
<SelectContent>
|
|
{tipos.map(t => <SelectItem key={t} value={t}>{t}</SelectItem>)}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<div>
|
|
<Label>Médico *</Label>
|
|
<Input value={getNombreProfesional(currentUser)} disabled placeholder="Nombre del médico" />
|
|
</div>
|
|
|
|
{(tipo === 'Farmacologica' || tipo === 'Farmacologica Antibiótico') && (
|
|
<>
|
|
<div><Label>Droga *</Label><Input value={droga} onChange={e => setDroga(e.target.value)} placeholder="Nombre del medicamento" /></div>
|
|
<div><Label>Dosis *</Label><Input value={dosis} onChange={e => setDosis(e.target.value)} placeholder="Ej: 500mg, 1g, 10ml" /></div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div><Label>Frecuencia (hs)</Label><Input type="number" value={frecuenciaHoras} onChange={e => setFrecuenciaHoras(e.target.value ? parseInt(e.target.value) : '')} placeholder="Cada X horas" /></div>
|
|
<div><Label>Vía</Label><Select value={via} onValueChange={(v: ViaAdministracion) => setVia(v)}><SelectTrigger><SelectValue /></SelectTrigger><SelectContent>{vias.map(v => <SelectItem key={v} value={v}>{v}</SelectItem>)}</SelectContent></Select></div>
|
|
</div>
|
|
</>
|
|
)}
|
|
{tipo === 'Farmacologica Profilactica' && (
|
|
<>
|
|
<div><Label>Droga *</Label><Input value={droga} onChange={e => setDroga(e.target.value)} placeholder="Nombre del medicamento" /></div>
|
|
<div><Label>Dosis *</Label><Input value={dosis} onChange={e => setDosis(e.target.value)} placeholder="Ej: 500mg, 1g, 10ml" /></div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div><Label>Frecuencia (hs)</Label><Input type="number" value={frecuenciaHoras} onChange={e => setFrecuenciaHoras(e.target.value ? parseInt(e.target.value) : '')} placeholder="Cada X horas" /></div>
|
|
<div><Label>Vía</Label><Select value={via} onValueChange={(v: ViaAdministracion) => setVia(v)}><SelectTrigger><SelectValue /></SelectTrigger><SelectContent>{vias.map(v => <SelectItem key={v} value={v}>{v}</SelectItem>)}</SelectContent></Select></div>
|
|
</div>
|
|
</>
|
|
)}
|
|
{tipo === 'Farmacologica Insulina' && (
|
|
<>
|
|
<div><Label>Tipo de Insulina</Label><Select value={tipoInsulina} onValueChange={(v: TipoInsulina) => setTipoInsulina(v)}><SelectTrigger><SelectValue /></SelectTrigger><SelectContent>{tiposInsulina.map(t => <SelectItem key={t} value={t}>{t}</SelectItem>)}</SelectContent></Select></div>
|
|
<div className="grid grid-cols-3 gap-2">
|
|
<div className="flex flex-col justify-end space-y-1.5">
|
|
<Label className="text-xs sm:text-sm">Pre Desayuno (U)</Label>
|
|
<Input type="number" value={unidadesDesayuno} onChange={e => setUnidadesDesayuno(e.target.value ? parseInt(e.target.value) : '')} placeholder="U" />
|
|
</div>
|
|
<div className="flex flex-col justify-end space-y-1.5">
|
|
<Label className="text-xs sm:text-sm">Pre Almuerzo (U)</Label>
|
|
<Input type="number" value={unidadesAlmuerzo} onChange={e => setUnidadesAlmuerzo(e.target.value ? parseInt(e.target.value) : '')} placeholder="U" />
|
|
</div>
|
|
<div className="flex flex-col justify-end space-y-1.5">
|
|
<Label className="text-xs sm:text-sm">23hs (U)</Label>
|
|
<Input type="number" value={unidadesNoche} onChange={e => setUnidadesNoche(e.target.value ? parseInt(e.target.value) : '')} placeholder="U" />
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{tipo === 'No Farmacologica' && (
|
|
<div><Label>Indicacionión *</Label><textarea className="w-full p-2 border rounded-md text-sm min-h-[80px] bg-transparent dark:bg-input/30" value={indicacionNoFco} onChange={e => setIndicacionNoFco(e.target.value)} placeholder="Ej: Control de signos vitales, Dieta, etc." /></div>
|
|
)}
|
|
|
|
{(tipo === 'PHP' || tipo === 'PHP Paralelo') && (
|
|
<>
|
|
<div><Label>Tipo de Plan</Label><Select value={tipoPlan} onValueChange={(v: TipoPlanHidratacion) => setTipoPlan(v)}><SelectTrigger><SelectValue /></SelectTrigger><SelectContent>{planes.map(p => <SelectItem key={p} value={p}>{p}</SelectItem>)}</SelectContent></Select></div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div><Label>Cantidad (ml) *</Label><Input type="number" value={cantidadMl} onChange={e => setCantidadMl(e.target.value ? parseInt(e.target.value) : '')} placeholder="ml" /></div>
|
|
<div><Label>Tiempo (hs)</Label><Input type="number" value={tiempoHoras} onChange={e => setTiempoHoras(e.target.value ? parseInt(e.target.value) : 24)} placeholder="24" /></div>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{tipo === 'PHP Alterno' && (
|
|
<>
|
|
<div><Label>Plan 1</Label><Select value={tipoPlan} onValueChange={(v: TipoPlanHidratacion) => setTipoPlan(v)}><SelectTrigger><SelectValue /></SelectTrigger><SelectContent>{planes.map(p => <SelectItem key={p} value={p}>{p}</SelectItem>)}</SelectContent></Select></div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div><Label>Cantidad Plan 1 (ml) *</Label><Input type="number" value={cantidadMl} onChange={e => setCantidadMl(e.target.value ? parseInt(e.target.value) : '')} placeholder="ml" /></div>
|
|
</div>
|
|
<div><Label>Plan 2</Label><Select value={tipoPlan2} onValueChange={(v: TipoPlanHidratacion) => setTipoPlan2(v)}><SelectTrigger><SelectValue /></SelectTrigger><SelectContent>{planes.map(p => <SelectItem key={p} value={p}>{p}</SelectItem>)}</SelectContent></Select></div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div><Label>Cantidad Plan 2 (ml) *</Label><Input type="number" value={cantidadMl2} onChange={e => setCantidadMl2(e.target.value ? parseInt(e.target.value) : '')} placeholder="ml" /></div>
|
|
<div><Label>Tiempo Total (hs)</Label><Input type="number" value={tiempoHoras} onChange={e => setTiempoHoras(e.target.value ? parseInt(e.target.value) : 24)} placeholder="24" /></div>
|
|
</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} disabled={!effectiveMedico.trim()}>Guardar</Button>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{activas.length === 0 ? (
|
|
<div className="text-center py-6 text-gray-500 text-sm">No hay indicaciones activas</div>
|
|
) : (
|
|
<div className="space-y-1.5">
|
|
{activas.map(ind => (
|
|
<Card key={ind.id}>
|
|
<CardContent className="py-2 px-3 flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-center gap-1.5 mb-0.5">
|
|
<Badge variant="outline" className="text-xs py-0 px-1.5 h-5">{ind.tipo}</Badge>
|
|
{ind.medicoCrea && <span className="text-xs text-muted-foreground">Dr: {ind.medicoCrea}</span>}
|
|
</div>
|
|
{ind.tipo === 'Farmacologica' && (
|
|
<div className="font-medium text-sm leading-tight">{ind.droga} {ind.dosis} {ind.frecuenciaHoras ? `c/${ind.frecuenciaHoras}hs` : ''} {ind.via}</div>
|
|
)}
|
|
{ind.tipo === 'Farmacologica Antibiótico' && (
|
|
<div className="font-medium text-sm leading-tight">{ind.droga} {ind.dosis} {ind.frecuenciaHoras ? `c/${ind.frecuenciaHoras}hs` : ''} {ind.via} <Badge className="ml-1.5 py-0 px-1.5 h-5 text-xs bg-purple-100 text-purple-800 dark:bg-purple-950 dark:text-purple-300 hover:bg-purple-100">Antibiótico</Badge></div>
|
|
)}
|
|
{ind.tipo === 'Farmacologica Profilactica' && (
|
|
<div className="font-medium text-sm leading-tight">{ind.droga} {ind.dosis} {ind.frecuenciaHoras ? `c/${ind.frecuenciaHoras}hs` : ''} {ind.via}</div>
|
|
)}
|
|
{ind.tipo === 'Farmacologica Insulina' && (
|
|
<div className="font-medium text-sm leading-tight">{ind.tipoInsulina}
|
|
{ind.unidadesDesayuno && ` - PreDesayuno: ${ind.unidadesDesayuno}U`}
|
|
{ind.unidadesAlmuerzo && ` - PreAlmuerzo: ${ind.unidadesAlmuerzo}U`}
|
|
{ind.unidadesNoche && ` - 23hs: ${ind.unidadesNoche}U`}
|
|
</div>
|
|
)}
|
|
{ind.tipo === 'No Farmacologica' && (
|
|
<div className="font-medium text-sm leading-tight">{ind.indicacionNoFco}</div>
|
|
)}
|
|
{(ind.tipo === 'PHP' || ind.tipo === 'PHP Paralelo') && (
|
|
<div className="font-medium text-sm leading-tight">{ind.tipoPlan} {ind.cantidadMl}ml en {ind.tiempoHoras}hs</div>
|
|
)}
|
|
{ind.tipo === 'PHP Alterno' && (
|
|
<div className="font-medium text-sm leading-tight">{ind.tipoPlan} {ind.cantidadMl}ml + {ind.tipoPlan2} {ind.cantidadMl2}ml en {ind.tiempoHoras}hs</div>
|
|
)}
|
|
</div>
|
|
<div className="flex gap-1 shrink-0">
|
|
{canEdit && <Button size="sm" variant="outline" className="h-7 w-7 p-0" title="Editar" onClick={() => loadEdit(ind)}>
|
|
<Pencil className="h-3.5 w-3.5" />
|
|
</Button>}
|
|
{canEdit && <Button size="sm" variant="outline" className="h-7 w-7 p-0 text-red-600 hover:text-red-700 hover:bg-red-50 dark:hover:bg-red-950/30" title="Eliminar" onClick={() => {
|
|
setDeleteConfirmInd(ind);
|
|
}}>
|
|
<Trash2 className="h-3.5 w-3.5" />
|
|
</Button>}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{deleteConfirmInd && (
|
|
<Dialog open={!!deleteConfirmInd} onOpenChange={(open) => { if (!open) setDeleteConfirmInd(null); }}>
|
|
<DialogContent className="max-w-md">
|
|
<DialogHeader>
|
|
<DialogTitle className="text-red-600 flex items-center gap-2">
|
|
<Trash2 className="h-5 w-5" />
|
|
Confirmar eliminación de indicación
|
|
</DialogTitle>
|
|
</DialogHeader>
|
|
<div className="space-y-4 py-2">
|
|
<p className="text-sm text-muted-foreground">
|
|
¿Está seguro de que desea suspender/eliminar esta indicación? Se registrará la baja en el historial.
|
|
</p>
|
|
<div className="p-3 bg-muted rounded-md text-sm font-medium">
|
|
<span className="text-xs text-muted-foreground block mb-1">Indicación:</span>
|
|
{formatIndicacion(deleteConfirmInd)}
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label htmlFor="delete-medico">Médico que suspende / elimina *</Label>
|
|
<Input
|
|
id="delete-medico"
|
|
value={getNombreProfesional(currentUser)}
|
|
disabled
|
|
placeholder="Nombre del profesional..."
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="flex justify-end gap-2 mt-2">
|
|
<Button variant="outline" onClick={() => setDeleteConfirmInd(null)}>
|
|
Cancelar
|
|
</Button>
|
|
<Button
|
|
variant="destructive"
|
|
onClick={async () => {
|
|
const med = effectiveDeleteMedico.trim() || deleteConfirmInd.medicoCrea || 'Médico';
|
|
const indToDel = deleteConfirmInd;
|
|
setDeleteConfirmInd(null);
|
|
await handleSuspender(indToDel, med);
|
|
}}
|
|
>
|
|
Eliminar
|
|
</Button>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)}
|
|
|
|
{showHistorial && (
|
|
<Dialog open={showHistorial} onOpenChange={setShowHistorial}>
|
|
<DialogContent className="max-w-4xl max-h-[80vh] overflow-auto">
|
|
<DialogHeader>
|
|
<DialogTitle>Historial de Indicaciones</DialogTitle>
|
|
</DialogHeader>
|
|
{movs.length === 0 ? (
|
|
<p className="text-gray-500 py-4">Sin movimientos registrados</p>
|
|
) : (
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead className="cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800" onClick={() => handleSort('fecha')}>
|
|
Fecha {sortField === 'fecha' && (sortDir === 'asc' ? '↑' : '↓')}
|
|
</TableHead>
|
|
<TableHead className="cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800" onClick={() => handleSort('profesional')}>
|
|
Profesional {sortField === 'profesional' && (sortDir === 'asc' ? '↑' : '↓')}
|
|
</TableHead>
|
|
<TableHead className="cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800" onClick={() => handleSort('tipo')}>
|
|
Tipo {sortField === 'tipo' && (sortDir === 'asc' ? '↑' : '↓')}
|
|
</TableHead>
|
|
<TableHead>Indicación</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{sortedMovs.map(mov => (
|
|
<TableRow key={mov.id}>
|
|
<TableCell>{mov.fecha}</TableCell>
|
|
<TableCell>{mov.profesional}</TableCell>
|
|
<TableCell>
|
|
<Badge className={
|
|
mov.tipo === 'Nueva' || mov.tipo === 'Indicacion'
|
|
? 'bg-green-600 hover:bg-green-700 text-white'
|
|
: mov.tipo === 'Suspencion' || mov.tipo === 'Suspensión' || mov.tipo === 'Eliminacion'
|
|
? 'bg-red-600 hover:bg-red-700 text-white'
|
|
: 'bg-orange-500 hover:bg-orange-600 text-white'
|
|
}>
|
|
{mov.tipo === 'Nueva' || mov.tipo === 'Indicacion' ? 'Nueva' : mov.tipo === 'Suspencion' || mov.tipo === 'Suspensión' || mov.tipo === 'Eliminacion' ? 'Suspensión' : 'Modificación'}
|
|
</Badge>
|
|
</TableCell>
|
|
<TableCell>
|
|
{mov.tipo === 'Modificacion' ? (
|
|
<div className="text-sm">
|
|
<div className="text-red-500 line-through">{mov.indicacionPrevia}</div>
|
|
<div className="text-green-500">{mov.indicacionNueva}</div>
|
|
</div>
|
|
) : (mov.tipo === 'Suspencion' || mov.tipo === 'Suspensión' || mov.tipo === 'Eliminacion') ? mov.indicacionPrevia : mov.indicacionNueva || mov.indicacionPrevia}
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
)}
|
|
</DialogContent>
|
|
</Dialog>
|
|
)}
|
|
</div>
|
|
);
|
|
} |