Agregar tab de pendientes, categoria procedimiento y fecha/hora programada
This commit is contained in:
@@ -16,6 +16,7 @@ import type {
|
||||
ATB,
|
||||
Indicacion,
|
||||
MovimientoIndicacion,
|
||||
Pendiente,
|
||||
Vista,
|
||||
Usuario
|
||||
} from '@/types';
|
||||
@@ -48,6 +49,7 @@ interface HospitalState {
|
||||
atb: ATB[];
|
||||
indicaciones: Indicacion[];
|
||||
movimientosIndicaciones: MovimientoIndicacion[];
|
||||
pendientes: Pendiente[];
|
||||
vistaActual: Vista;
|
||||
currentInternacionId?: string | null;
|
||||
usuarios: Usuario[];
|
||||
@@ -73,6 +75,7 @@ const defaultState = (): HospitalState => ({
|
||||
vistaActual: 'dashboard',
|
||||
indicaciones: [],
|
||||
movimientosIndicaciones: [],
|
||||
pendientes: [],
|
||||
camas: [],
|
||||
usuarios: [],
|
||||
currentUser: null,
|
||||
@@ -104,6 +107,7 @@ export function useHospitalStore() {
|
||||
atb: body.atb || [],
|
||||
glucemias: body.glucemias || [],
|
||||
movimientosIndicaciones: body.movimientosIndicaciones || [],
|
||||
pendientes: body.pendientes || [],
|
||||
currentInternacionId: storedInternacionId || body.currentInternacionId || null,
|
||||
currentUser,
|
||||
isAuthenticated: !!currentUser,
|
||||
@@ -1293,6 +1297,60 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
||||
.sort((a, b) => new Date(b.fechaToma).getTime() - new Date(a.fechaToma).getTime());
|
||||
}, [state.cultivos]);
|
||||
|
||||
const agregarPendiente = useCallback(async (datos: Omit<Pendiente, 'id'>) => {
|
||||
const nuevoPendiente: Pendiente = {
|
||||
...datos,
|
||||
id: generateUUID(),
|
||||
fechaCreacion: datos.fechaCreacion || new Date().toISOString().split('T')[0],
|
||||
horaCreacion: datos.horaCreacion || new Date().toTimeString().slice(0, 5),
|
||||
estado: datos.estado || 'pendiente',
|
||||
prioridad: datos.prioridad || 'media',
|
||||
};
|
||||
try {
|
||||
await apiCall('POST', '/pendientes', nuevoPendiente);
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
pendientes: [...(prev.pendientes || []), nuevoPendiente],
|
||||
}));
|
||||
return nuevoPendiente.id;
|
||||
} catch (err) {
|
||||
console.error('Error al agregar pendiente:', err);
|
||||
throw err;
|
||||
}
|
||||
}, [apiCall]);
|
||||
|
||||
const actualizarPendiente = useCallback(async (id: string, datos: Partial<Pendiente>) => {
|
||||
try {
|
||||
await apiCall('PUT', `/pendientes/${id}`, datos);
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
pendientes: (prev.pendientes || []).map(p => p.id === id ? { ...p, ...datos } : p),
|
||||
}));
|
||||
} catch (err) {
|
||||
console.error('Error al actualizar pendiente:', err);
|
||||
throw err;
|
||||
}
|
||||
}, [apiCall]);
|
||||
|
||||
const eliminarPendiente = useCallback(async (id: string) => {
|
||||
try {
|
||||
await apiCall('DELETE', `/pendientes/${id}`);
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
pendientes: (prev.pendientes || []).filter(p => p.id !== id),
|
||||
}));
|
||||
} catch (err) {
|
||||
console.error('Error al eliminar pendiente:', err);
|
||||
throw err;
|
||||
}
|
||||
}, [apiCall]);
|
||||
|
||||
const getPendientesByPaciente = useCallback((pacienteId: string) => {
|
||||
return (state.pendientes || [])
|
||||
.filter(p => p.pacienteId === pacienteId)
|
||||
.sort((a, b) => new Date(`${b.fechaCreacion}T${b.horaCreacion || '00:00'}`).getTime() - new Date(`${a.fechaCreacion}T${a.horaCreacion || '00:00'}`).getTime());
|
||||
}, [state.pendientes]);
|
||||
|
||||
const getEstadisticas = useCallback(() => {
|
||||
const camasEnArea = state.camas.filter(c => !isCamaFueraDeGrupo(c));
|
||||
const camasFueraDeArea = state.camas.filter(c => isCamaFueraDeGrupo(c));
|
||||
@@ -1434,6 +1492,10 @@ const actualizarInternacion = useCallback(async (internacionId: string, datos: P
|
||||
getGlucemiasByInternacion,
|
||||
getAcidosBaseByInternacion,
|
||||
getCultivosByInternacion,
|
||||
agregarPendiente,
|
||||
actualizarPendiente,
|
||||
eliminarPendiente,
|
||||
getPendientesByPaciente,
|
||||
setCurrentInternacion,
|
||||
getEstadisticas,
|
||||
login,
|
||||
|
||||
@@ -37,13 +37,20 @@ import {
|
||||
TrendingUp,
|
||||
Activity,
|
||||
Pill,
|
||||
Users
|
||||
Users,
|
||||
ListTodo,
|
||||
CheckSquare,
|
||||
Square,
|
||||
Copy,
|
||||
Clock,
|
||||
Search
|
||||
} from 'lucide-react';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
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 } from '@/components/ui/select';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
@@ -51,7 +58,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, Glucemia, AcidoBase, Cultivo, Paciente, ResultadoLaboratorio, Internacion, Evolucion, Cama, EstudioComplementario, Interconsulta, ATB, Indicacion, MovimientoIndicacion } from '@/types';
|
||||
import type { Laboratorio, Glucemia, AcidoBase, Cultivo, Paciente, ResultadoLaboratorio, Internacion, Evolucion, Cama, EstudioComplementario, Interconsulta, ATB, Indicacion, MovimientoIndicacion, Pendiente } from '@/types';
|
||||
import { formatDateDDMMYYYY } from '@/lib/utils';
|
||||
import { SeccionIndicaciones } from './SeccionIndicaciones';
|
||||
|
||||
@@ -241,7 +248,9 @@ export function HistoriaClinica({
|
||||
canEdit,
|
||||
}: HistoriaClinicaProps) {
|
||||
const [tabActivo, setTabActivo] = useState('evoluciones');
|
||||
const { pendientes } = useHospitalStore();
|
||||
const indicacionesActivas = (indicadores || []).filter(i => i.estado === 'Activa');
|
||||
const pendientesActivos = (pendientes || []).filter(p => p.pacienteId === paciente.id && p.estado === 'pendiente');
|
||||
|
||||
|
||||
const calcularEdad = (fechaNacimiento: string) => {
|
||||
@@ -554,6 +563,11 @@ export function HistoriaClinica({
|
||||
<span>Interconsultas</span>
|
||||
({interconsultas.length})
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="pendientes">
|
||||
<ListTodo className="h-3 w-3 sm:h-4 sm:w-4" />
|
||||
<span>Pendientes</span>
|
||||
({pendientesActivos.length})
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<ScrollBar orientation="horizontal" />
|
||||
</ScrollArea>
|
||||
@@ -630,6 +644,14 @@ export function HistoriaClinica({
|
||||
canEdit={canEdit}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="pendientes" className="mt-4">
|
||||
<SeccionPendientes
|
||||
pacienteId={paciente.id}
|
||||
internacionId={internacion.id}
|
||||
canEdit={canEdit}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2984,4 +3006,423 @@ function SeccionATB({ atb, internacionId, pacienteId, add, update, del, canEdit
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SeccionPendientes({ pacienteId, internacionId, canEdit }: { pacienteId: string; internacionId: string; canEdit: boolean }) {
|
||||
const { pendientes, agregarPendiente, actualizarPendiente, eliminarPendiente, currentUser } = useHospitalStore();
|
||||
const [modalAbierto, setModalAbierto] = useState(false);
|
||||
const [pendienteEditar, setPendienteEditar] = useState<Pendiente | null>(null);
|
||||
|
||||
// Form fields
|
||||
const [descripcion, setDescripcion] = useState('');
|
||||
const [categoria, setCategoria] = useState<Pendiente['categoria']>('General');
|
||||
const [prioridad, setPrioridad] = useState<Pendiente['prioridad']>('media');
|
||||
const [observaciones, setObservaciones] = useState('');
|
||||
const [fechaProgramada, setFechaProgramada] = useState('');
|
||||
const [horaProgramada, setHoraProgramada] = useState('');
|
||||
|
||||
// Filters
|
||||
const [filtroEstado, setFiltroEstado] = useState<'todos' | 'pendiente' | 'realizado' | 'cancelado'>('pendiente');
|
||||
const [filtroCategoria, setFiltroCategoria] = useState<string>('todas');
|
||||
const [busqueda, setBusqueda] = useState('');
|
||||
|
||||
const misPendientes = (pendientes || []).filter(p => p.pacienteId === pacienteId || p.internacionId === internacionId);
|
||||
|
||||
const pendientesFiltrados = misPendientes.filter(p => {
|
||||
if (filtroEstado !== 'todos' && p.estado !== filtroEstado) return false;
|
||||
if (filtroCategoria !== 'todas' && p.categoria !== filtroCategoria) return false;
|
||||
if (busqueda.trim()) {
|
||||
const query = busqueda.toLowerCase();
|
||||
const descMatch = p.descripcion.toLowerCase().includes(query);
|
||||
const obsMatch = (p.observaciones || '').toLowerCase().includes(query);
|
||||
const profMatch = (p.profesional || '').toLowerCase().includes(query);
|
||||
if (!descMatch && !obsMatch && !profMatch) return false;
|
||||
}
|
||||
return true;
|
||||
}).sort((a, b) => {
|
||||
if (a.estado === 'pendiente' && b.estado !== 'pendiente') return -1;
|
||||
if (a.estado !== 'pendiente' && b.estado === 'pendiente') return 1;
|
||||
const prioWeight = { alta: 3, media: 2, baja: 1 };
|
||||
if (prioWeight[b.prioridad] !== prioWeight[a.prioridad]) {
|
||||
return prioWeight[b.prioridad] - prioWeight[a.prioridad];
|
||||
}
|
||||
return new Date(`${b.fechaCreacion}T${b.horaCreacion || '00:00'}`).getTime() - new Date(`${a.fechaCreacion}T${a.horaCreacion || '00:00'}`).getTime();
|
||||
});
|
||||
|
||||
const resetForm = () => {
|
||||
setDescripcion('');
|
||||
setCategoria('General');
|
||||
setPrioridad('media');
|
||||
setObservaciones('');
|
||||
setFechaProgramada('');
|
||||
setHoraProgramada('');
|
||||
setPendienteEditar(null);
|
||||
};
|
||||
|
||||
const openEditar = (p: Pendiente) => {
|
||||
setPendienteEditar(p);
|
||||
setDescripcion(p.descripcion);
|
||||
setCategoria(p.categoria);
|
||||
setPrioridad(p.prioridad);
|
||||
setObservaciones(p.observaciones || '');
|
||||
setFechaProgramada(p.fechaProgramada || '');
|
||||
setHoraProgramada(p.horaProgramada || '');
|
||||
setModalAbierto(true);
|
||||
};
|
||||
|
||||
const handleGuardar = async () => {
|
||||
if (!descripcion.trim()) {
|
||||
toast.error('La descripción del pendiente es obligatoria');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const payload: Partial<Pendiente> = {
|
||||
descripcion,
|
||||
categoria,
|
||||
prioridad,
|
||||
observaciones: observaciones.trim() || undefined,
|
||||
fechaProgramada: (categoria === 'Estudio' || categoria === 'Procedimiento') ? (fechaProgramada || undefined) : undefined,
|
||||
horaProgramada: (categoria === 'Estudio' || categoria === 'Procedimiento') ? (horaProgramada || undefined) : undefined,
|
||||
};
|
||||
|
||||
if (pendienteEditar) {
|
||||
await actualizarPendiente(pendienteEditar.id, payload);
|
||||
toast.success('Pendiente actualizado con éxito');
|
||||
} else {
|
||||
const profesional = currentUser ? getNombreProfesional(currentUser) : 'Sistema';
|
||||
await agregarPendiente({
|
||||
pacienteId,
|
||||
internacionId,
|
||||
...payload,
|
||||
estado: 'pendiente',
|
||||
fechaCreacion: new Date().toISOString().split('T')[0],
|
||||
horaCreacion: new Date().toTimeString().slice(0, 5),
|
||||
profesional,
|
||||
});
|
||||
toast.success('Pendiente añadido con éxito');
|
||||
}
|
||||
setModalAbierto(false);
|
||||
resetForm();
|
||||
} catch {
|
||||
toast.error('Error al guardar el pendiente');
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggleEstado = async (p: Pendiente) => {
|
||||
const nuevoEstado = p.estado === 'pendiente' ? 'realizado' : 'pendiente';
|
||||
try {
|
||||
await actualizarPendiente(p.id, {
|
||||
estado: nuevoEstado,
|
||||
fechaRealizado: nuevoEstado === 'realizado' ? new Date().toISOString().split('T')[0] : undefined,
|
||||
usuarioRealizado: nuevoEstado === 'realizado' && currentUser ? getNombreProfesional(currentUser) : undefined,
|
||||
});
|
||||
toast.success(nuevoEstado === 'realizado' ? 'Marcado como realizado' : 'Marcado como pendiente');
|
||||
} catch {
|
||||
toast.error('Error al cambiar estado');
|
||||
}
|
||||
};
|
||||
|
||||
const handleEliminar = async (id: string) => {
|
||||
try {
|
||||
await eliminarPendiente(id);
|
||||
toast.success('Pendiente eliminado');
|
||||
} catch {
|
||||
toast.error('Error al eliminar');
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopiarPendientes = () => {
|
||||
const activos = misPendientes.filter(p => p.estado === 'pendiente');
|
||||
if (activos.length === 0) {
|
||||
toast.info('No hay pendientes activos para copiar');
|
||||
return;
|
||||
}
|
||||
const texto = activos.map((p, idx) => {
|
||||
let line = `${idx + 1}. [${p.categoria.toUpperCase()}] ${p.descripcion}`;
|
||||
if (p.fechaProgramada) line += ` (Programado: ${formatDateDDMMYYYY(p.fechaProgramada)}${p.horaProgramada ? ' ' + p.horaProgramada : ''})`;
|
||||
if (p.prioridad === 'alta') line += ' (ALTA PRIORIDAD)';
|
||||
if (p.observaciones) line += ` - Obs: ${p.observaciones}`;
|
||||
return line;
|
||||
}).join('\n');
|
||||
|
||||
navigator.clipboard.writeText(`PENDIENTES DEL PACIENTE:\n${texto}`);
|
||||
toast.success('Pendientes copiados al portapapeles');
|
||||
};
|
||||
|
||||
const getPriorityBadge = (prio: Pendiente['prioridad']) => {
|
||||
switch (prio) {
|
||||
case 'alta':
|
||||
return <Badge className="bg-red-100 text-red-800 dark:bg-red-900/60 dark:text-red-200 border-red-200">Alta</Badge>;
|
||||
case 'media':
|
||||
return <Badge className="bg-amber-100 text-amber-800 dark:bg-amber-900/60 dark:text-amber-200 border-amber-200">Media</Badge>;
|
||||
case 'baja':
|
||||
return <Badge className="bg-blue-100 text-blue-800 dark:bg-blue-900/60 dark:text-blue-200 border-blue-200">Baja</Badge>;
|
||||
}
|
||||
};
|
||||
|
||||
const getCategoryBadge = (cat: Pendiente['categoria']) => {
|
||||
return <Badge variant="outline" className="text-xs">{cat}</Badge>;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Header controls */}
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 bg-card p-4 rounded-lg border shadow-sm">
|
||||
<div className="flex flex-wrap items-center gap-2 w-full sm:w-auto">
|
||||
<div className="relative flex-1 sm:w-60">
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Buscar pendientes..."
|
||||
value={busqueda}
|
||||
onChange={e => setBusqueda(e.target.value)}
|
||||
className="pl-8 h-9 text-xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Select value={filtroEstado} onValueChange={(val: 'todos' | 'pendiente' | 'realizado' | 'cancelado') => setFiltroEstado(val)}>
|
||||
<SelectTrigger className="w-[130px] h-9 text-xs">
|
||||
<SelectValue placeholder="Estado" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="pendiente">Pendientes</SelectItem>
|
||||
<SelectItem value="realizado">Realizados</SelectItem>
|
||||
<SelectItem value="cancelado">Cancelados</SelectItem>
|
||||
<SelectItem value="todos">Todos</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select value={filtroCategoria} onValueChange={setFiltroCategoria}>
|
||||
<SelectTrigger className="w-[150px] h-9 text-xs">
|
||||
<SelectValue placeholder="Categoría" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="todas">Todas las cat.</SelectItem>
|
||||
<SelectItem value="General">General</SelectItem>
|
||||
<SelectItem value="Estudio">Estudio</SelectItem>
|
||||
<SelectItem value="Procedimiento">Procedimiento</SelectItem>
|
||||
<SelectItem value="Laboratorio">Laboratorio</SelectItem>
|
||||
<SelectItem value="Interconsulta">Interconsulta</SelectItem>
|
||||
<SelectItem value="Tratamiento">Tratamiento</SelectItem>
|
||||
<SelectItem value="Administrativo">Administrativo</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 w-full sm:w-auto justify-end">
|
||||
<Button variant="outline" size="sm" onClick={handleCopiarPendientes} title="Copiar pendientes activos">
|
||||
<Copy className="h-4 w-4 mr-1.5" />
|
||||
Copiar
|
||||
</Button>
|
||||
{canEdit && (
|
||||
<Button size="sm" onClick={() => { resetForm(); setModalAbierto(true); }}>
|
||||
<Plus className="h-4 w-4 mr-1.5" />
|
||||
Nuevo Pendiente
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* List of Pendientes */}
|
||||
{pendientesFiltrados.length === 0 ? (
|
||||
<Card className="p-8 text-center text-muted-foreground border-dashed">
|
||||
<ListTodo className="h-10 w-10 mx-auto mb-2 opacity-40" />
|
||||
<p className="font-medium">No se encontraron registros de pendientes</p>
|
||||
<p className="text-xs mt-1">
|
||||
{misPendientes.length === 0
|
||||
? 'Utilice el botón "Nuevo Pendiente" para agregar una tarea pendiente para este paciente.'
|
||||
: 'Pruebe cambiar los filtros para ver otros registros.'}
|
||||
</p>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="space-y-2.5">
|
||||
{pendientesFiltrados.map(p => (
|
||||
<Card
|
||||
key={p.id}
|
||||
className={`transition-colors ${
|
||||
p.estado === 'realizado' ? 'bg-muted/30 border-muted opacity-75' : 'hover:border-primary/50'
|
||||
}`}
|
||||
>
|
||||
<CardContent className="p-4 flex items-start gap-3">
|
||||
{canEdit && (
|
||||
<button
|
||||
onClick={() => handleToggleEstado(p)}
|
||||
className="mt-0.5 text-muted-foreground hover:text-primary transition-colors focus:outline-none"
|
||||
title={p.estado === 'pendiente' ? 'Marcar como realizado' : 'Marcar como pendiente'}
|
||||
>
|
||||
{p.estado === 'realizado' ? (
|
||||
<CheckSquare className="h-5 w-5 text-emerald-600 dark:text-emerald-400" />
|
||||
) : (
|
||||
<Square className="h-5 w-5" />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
|
||||
<div className="flex-1 min-w-0 space-y-1">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className={`font-medium text-sm ${p.estado === 'realizado' ? 'line-through text-muted-foreground' : ''}`}>
|
||||
{p.descripcion}
|
||||
</span>
|
||||
{getPriorityBadge(p.prioridad)}
|
||||
{getCategoryBadge(p.categoria)}
|
||||
{p.estado === 'realizado' && (
|
||||
<Badge className="bg-emerald-100 text-emerald-800 dark:bg-emerald-900/60 dark:text-emerald-200">
|
||||
Realizado
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{p.fechaProgramada && (
|
||||
<div className="flex items-center gap-1.5 text-xs font-semibold text-blue-700 dark:text-blue-300 bg-blue-50 dark:bg-blue-950/40 px-2 py-1 rounded w-fit">
|
||||
<Clock className="h-3.5 w-3.5" />
|
||||
Programado: {formatDateDDMMYYYY(p.fechaProgramada)} {p.horaProgramada ? `a las ${p.horaProgramada} hs` : ''}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{p.observaciones && (
|
||||
<p className="text-xs text-muted-foreground bg-muted/40 p-2 rounded border mt-1">
|
||||
{p.observaciones}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="flex flex-wrap items-center gap-3 text-[11px] text-muted-foreground pt-1">
|
||||
<span className="flex items-center gap-1">
|
||||
<Clock className="h-3 w-3" />
|
||||
Creado: {formatDateDDMMYYYY(p.fechaCreacion)} {p.horaCreacion || ''}
|
||||
</span>
|
||||
{p.profesional && <span>Por: {p.profesional}</span>}
|
||||
{p.fechaRealizado && (
|
||||
<span className="text-emerald-600 dark:text-emerald-400 font-medium">
|
||||
Realizado el {formatDateDDMMYYYY(p.fechaRealizado)} {p.usuarioRealizado ? `por ${p.usuarioRealizado}` : ''}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{canEdit && (
|
||||
<div className="flex items-center gap-1">
|
||||
<Button size="icon" variant="ghost" className="h-7 w-7" onClick={() => openEditar(p)} title="Editar">
|
||||
<Pencil className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-7 w-7 text-red-600 hover:text-red-700 hover:bg-red-50 dark:hover:bg-red-950/40"
|
||||
onClick={() => handleEliminar(p.id)}
|
||||
title="Eliminar pendiente"
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Modal Crear/Editar */}
|
||||
<Dialog open={modalAbierto} onOpenChange={setModalAbierto}>
|
||||
<DialogContent className="sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<ListTodo className="h-5 w-5 text-blue-600" />
|
||||
{pendienteEditar ? 'Editar Pendiente' : 'Nuevo Pendiente de Paciente'}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 pt-2">
|
||||
<div>
|
||||
<Label className="text-xs font-medium">Descripción del Pendiente *</Label>
|
||||
<Textarea
|
||||
placeholder="Ej: Solicitar ecografía abdominal, Chequear laboratorio de control, Pendiente interconsulta con Cardiología..."
|
||||
value={descripcion}
|
||||
onChange={e => setDescripcion(e.target.value)}
|
||||
rows={3}
|
||||
className="mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<Label className="text-xs font-medium">Categoría</Label>
|
||||
<Select value={categoria} onValueChange={(val: Pendiente['categoria']) => setCategoria(val)}>
|
||||
<SelectTrigger className="mt-1">
|
||||
<SelectValue placeholder="Categoría" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="General">General</SelectItem>
|
||||
<SelectItem value="Estudio">Estudio</SelectItem>
|
||||
<SelectItem value="Procedimiento">Procedimiento</SelectItem>
|
||||
<SelectItem value="Laboratorio">Laboratorio</SelectItem>
|
||||
<SelectItem value="Interconsulta">Interconsulta</SelectItem>
|
||||
<SelectItem value="Tratamiento">Tratamiento</SelectItem>
|
||||
<SelectItem value="Administrativo">Administrativo</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label className="text-xs font-medium">Prioridad</Label>
|
||||
<Select value={prioridad} onValueChange={(val: Pendiente['prioridad']) => setPrioridad(val)}>
|
||||
<SelectTrigger className="mt-1">
|
||||
<SelectValue placeholder="Prioridad" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="alta">Alta</SelectItem>
|
||||
<SelectItem value="media">Media</SelectItem>
|
||||
<SelectItem value="baja">Baja</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{(categoria === 'Estudio' || categoria === 'Procedimiento') && (
|
||||
<div className="grid grid-cols-2 gap-3 p-3 bg-muted/30 rounded-lg border">
|
||||
<div>
|
||||
<Label className="text-xs font-medium">Fecha Programada</Label>
|
||||
<Input
|
||||
type="date"
|
||||
value={fechaProgramada}
|
||||
onChange={e => setFechaProgramada(e.target.value)}
|
||||
className="mt-1 h-9 text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-xs font-medium">Hora Programada</Label>
|
||||
<Input
|
||||
type="time"
|
||||
value={horaProgramada}
|
||||
onChange={e => setHoraProgramada(e.target.value)}
|
||||
className="mt-1 h-9 text-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<Label className="text-xs font-medium">Observaciones / Detalles (opcional)</Label>
|
||||
<Textarea
|
||||
placeholder="Detalles adicionales, indicaciones específicas..."
|
||||
value={observaciones}
|
||||
onChange={e => setObservaciones(e.target.value)}
|
||||
rows={2}
|
||||
className="mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2 mt-4 pt-2 border-t">
|
||||
<Button variant="outline" onClick={() => setModalAbierto(false)}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button onClick={handleGuardar}>
|
||||
<Save className="h-4 w-4 mr-1.5" />
|
||||
Guardar
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -254,3 +254,21 @@ export interface Usuario {
|
||||
areaId?: string;
|
||||
fechaCreacion: string;
|
||||
}
|
||||
|
||||
export interface Pendiente {
|
||||
id: string;
|
||||
pacienteId: string;
|
||||
internacionId?: string;
|
||||
descripcion: string;
|
||||
prioridad: 'alta' | 'media' | 'baja';
|
||||
estado: 'pendiente' | 'realizado' | 'cancelado';
|
||||
fechaCreacion: string;
|
||||
horaCreacion?: string;
|
||||
fechaRealizado?: string;
|
||||
categoria?: 'General' | 'Estudio' | 'Procedimiento' | 'Laboratorio' | 'Interconsulta' | 'Tratamiento' | 'Administrativo' | string;
|
||||
fechaProgramada?: string;
|
||||
horaProgramada?: string;
|
||||
usuarioId?: string;
|
||||
usuarioNombre?: string;
|
||||
observaciones?: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user