Agregar tab de pendientes, categoria procedimiento y fecha/hora programada

This commit is contained in:
2026-08-11 20:20:04 +00:00
parent ca2f8a32e1
commit a700c40d20
5 changed files with 599 additions and 2 deletions
+39
View File
@@ -16,6 +16,7 @@ import { initDb,
getAllAtb, createAtb, updateAtb, deleteAtb,
getAllIndicaciones, createIndicacion, updateIndicacion, deleteIndicacion,
getAllMovimientosIndicaciones, createMovimientoIndicacion,
getAllPendientes, createPendiente, updatePendiente, deletePendiente,
getValue, setValue
} from './db-mongodb.js';
@@ -63,6 +64,7 @@ app.get('/api/state', async (req, res) => {
atb: await getAllAtb(),
indicaciones: await getAllIndicaciones(),
movimientosIndicaciones: await getAllMovimientosIndicaciones(),
pendientes: await getAllPendientes(),
usuarios: usuariosList.map(({ passwordHash, ...u }) => u),
vistaActual: 'dashboard',
currentInternacionId: null
@@ -656,6 +658,43 @@ app.post('/api/movimientos-indicaciones', async (req, res) => {
}
});
// ========== PENDIENTES ==========
app.get('/api/pendientes', async (req, res) => {
try {
res.json(await getAllPendientes());
} catch (err) {
res.status(500).json({ error: 'Error al obtener pendientes' });
}
});
app.post('/api/pendientes', async (req, res) => {
try {
const pendiente = { ...req.body, id: req.body.id || generateUUID() };
await createPendiente(pendiente);
res.json(pendiente);
} catch (err) {
res.status(500).json({ error: 'Error al crear pendiente' });
}
});
app.put('/api/pendientes/:id', async (req, res) => {
try {
await updatePendiente(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al actualizar pendiente' });
}
});
app.delete('/api/pendientes/:id', async (req, res) => {
try {
await deletePendiente(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: 'Error al eliminar pendiente' });
}
});
// ========== AUTH ==========
app.post('/api/auth/login', async (req, res) => {
try {
+37
View File
@@ -64,6 +64,7 @@ const memStore = {
atb: [],
indicaciones: [],
movimientos_indicaciones: [],
pendientes: [],
kv: {}
};
@@ -935,6 +936,42 @@ export async function createMovimientoIndicacion(mov) {
}
}
// ========== PENDIENTES ==========
export async function getAllPendientes() {
if (db) {
const pendientes = await db.collection('pendientes').find().toArray();
return cleanDocs(pendientes);
}
return [...memStore.pendientes];
}
export async function createPendiente(pendiente) {
if (db) {
await db.collection('pendientes').insertOne(pendiente);
} else {
memStore.pendientes.push(pendiente);
}
}
export async function updatePendiente(id, datos) {
if (db) {
await db.collection('pendientes').updateOne({ id }, { $set: datos });
} else {
const idx = memStore.pendientes.findIndex(p => p.id === id);
if (idx !== -1) {
memStore.pendientes[idx] = { ...memStore.pendientes[idx], ...datos };
}
}
}
export async function deletePendiente(id) {
if (db) {
await db.collection('pendientes').deleteOne({ id });
} else {
memStore.pendientes = memStore.pendientes.filter(p => p.id !== id);
}
}
// ========== KV STORE ==========
export async function getValue(key) {
if (db) {
+62
View File
@@ -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,
+443 -2
View File
@@ -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>
@@ -2985,3 +3007,422 @@ 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>
);
}
+18
View File
@@ -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;
}