319 lines
13 KiB
TypeScript
319 lines
13 KiB
TypeScript
import {
|
|
Bed,
|
|
Users,
|
|
ClipboardList,
|
|
FlaskConical,
|
|
Activity,
|
|
Microscope,
|
|
TrendingUp,
|
|
AlertCircle,
|
|
CheckCircle2,
|
|
Clock
|
|
} from 'lucide-react';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Button } from '@/components/ui/button';
|
|
import { formatDateDDMMYYYY } from '@/lib/utils';
|
|
import type { Vista, Internacion, Paciente, Cultivo } from '@/types';
|
|
|
|
interface DashboardProps {
|
|
estadisticas: {
|
|
camasOcupadas: number;
|
|
camasDisponibles: number;
|
|
camasMantenimiento: number;
|
|
totalCamas: number;
|
|
totalCamasActivas: number;
|
|
camasFueraDeArea: number;
|
|
internacionesActivas: number;
|
|
totalPacientes: number;
|
|
cultivosPendientes: number;
|
|
porcentajeOcupacion: number;
|
|
};
|
|
internaciones: Internacion[];
|
|
cultivos: Cultivo[];
|
|
onCambiarVista: (vista: Vista) => void;
|
|
getPacienteById: (id: string) => Paciente | undefined;
|
|
}
|
|
|
|
export function Dashboard({
|
|
estadisticas,
|
|
internaciones,
|
|
cultivos,
|
|
onCambiarVista,
|
|
getPacienteById
|
|
}: DashboardProps) {
|
|
const internacionesActivas = internaciones.filter(i => i.activa).slice(0, 5);
|
|
const cultivosRecientes = cultivos
|
|
.filter(c => c.estado === 'NAF/Pendiente')
|
|
.slice(0, 5);
|
|
|
|
return (
|
|
<div className="space-y-6 dark:text-white">
|
|
{/* Header */}
|
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Dashboard</h1>
|
|
<p className="text-gray-500 dark:text-gray-400">Resumen del servicio - Clinica Medica</p>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Badge variant="outline" className="bg-green-50 text-green-700 border-green-200 dark:bg-green-900 dark:text-green-300 dark:border-green-700">
|
|
<CheckCircle2 className="h-3 w-3 mr-1" />
|
|
Sistema Operativo
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Estadísticas Principales */}
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
<Card className="border-l-4 border-l-blue-500">
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm font-medium text-gray-500 flex items-center gap-2">
|
|
<Bed className="h-4 w-4" />
|
|
Ocupación de Camas
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="flex items-baseline gap-2">
|
|
<span className="text-3xl font-bold">{estadisticas.porcentajeOcupacion}%</span>
|
|
<span className="text-sm text-gray-500">{estadisticas.camasOcupadas}/{estadisticas.totalCamas}</span>
|
|
</div>
|
|
<p className="text-xs text-gray-400 mt-1">
|
|
{estadisticas.camasDisponibles} camas disponibles
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="border-l-4 border-l-green-500">
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm font-medium text-gray-500 flex items-center gap-2">
|
|
<Users className="h-4 w-4" />
|
|
Camas Fuera de Área
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="flex items-baseline gap-2">
|
|
<span className="text-3xl font-bold">{estadisticas.camasFueraDeArea}</span>
|
|
</div>
|
|
<p className="text-xs text-gray-400 mt-1">
|
|
Camas Fuera de Area
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="border-l-4 border-l-purple-500">
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm font-medium text-gray-500 flex items-center gap-2">
|
|
<ClipboardList className="h-4 w-4" />
|
|
Internaciones Activas
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="flex items-baseline gap-2">
|
|
<span className="text-3xl font-bold">{estadisticas.internacionesActivas}</span>
|
|
</div>
|
|
<p className="text-xs text-gray-400 mt-1">
|
|
En curso actualmente
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="border-l-4 border-l-amber-500">
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm font-medium text-gray-500 flex items-center gap-2">
|
|
<Microscope className="h-4 w-4" />
|
|
Cultivos Pendientes
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="flex items-baseline gap-2">
|
|
<span className="text-3xl font-bold">{estadisticas.cultivosPendientes}</span>
|
|
</div>
|
|
<p className="text-xs text-gray-400 mt-1">
|
|
Esperando resultados
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Contenido Principal */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
{/* Estado de Camas */}
|
|
<Card>
|
|
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
|
<CardTitle className="text-lg flex items-center gap-2">
|
|
<Bed className="h-5 w-5 text-blue-600" />
|
|
Estado de Camas
|
|
</CardTitle>
|
|
<Button variant="ghost" size="sm" onClick={() => onCambiarVista('camas')}>
|
|
Ver todas
|
|
</Button>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="bg-green-50 p-3 rounded-lg">
|
|
<div className="flex items-center gap-2">
|
|
<CheckCircle2 className="h-4 w-4 text-green-600" />
|
|
<span className="text-sm font-medium text-green-800">Disponibles</span>
|
|
</div>
|
|
<p className="text-2xl font-bold text-green-700 mt-1">{estadisticas.camasDisponibles}</p>
|
|
</div>
|
|
<div className="bg-red-50 p-3 rounded-lg">
|
|
<div className="flex items-center gap-2">
|
|
<AlertCircle className="h-4 w-4 text-red-600" />
|
|
<span className="text-sm font-medium text-red-800">Ocupadas</span>
|
|
</div>
|
|
<p className="text-2xl font-bold text-red-700 mt-1">{estadisticas.camasOcupadas}</p>
|
|
</div>
|
|
<div className="bg-amber-50 p-3 rounded-lg">
|
|
<div className="flex items-center gap-2">
|
|
<Clock className="h-4 w-4 text-amber-600" />
|
|
<span className="text-sm font-medium text-amber-800">Mantenimiento</span>
|
|
</div>
|
|
<p className="text-2xl font-bold text-amber-700 mt-1">{estadisticas.camasMantenimiento}</p>
|
|
</div>
|
|
<div className="bg-blue-50 p-3 rounded-lg">
|
|
<div className="flex items-center gap-2">
|
|
<TrendingUp className="h-4 w-4 text-blue-600" />
|
|
<span className="text-sm font-medium text-blue-800">Ocupación</span>
|
|
</div>
|
|
<p className="text-2xl font-bold text-blue-700 mt-1">{estadisticas.porcentajeOcupacion}%</p>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Internaciones Activas */}
|
|
<Card>
|
|
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
|
<CardTitle className="text-lg flex items-center gap-2">
|
|
<ClipboardList className="h-5 w-5 text-purple-600" />
|
|
Internaciones Activas
|
|
</CardTitle>
|
|
<Button variant="ghost" size="sm" onClick={() => onCambiarVista('internaciones')}>
|
|
Ver todas
|
|
</Button>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{internacionesActivas.length === 0 ? (
|
|
<div className="text-center py-8 text-gray-400">
|
|
<ClipboardList className="h-12 w-12 mx-auto mb-2 opacity-50" />
|
|
<p>No hay internaciones activas</p>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{internacionesActivas.map((internacion) => {
|
|
const paciente = getPacienteById(internacion.pacienteId);
|
|
return (
|
|
<div key={internacion.id} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
|
<div>
|
|
<p className="font-medium text-gray-900">
|
|
{paciente ? `${paciente.apellido}, ${paciente.nombre}` : 'Paciente no encontrado'}
|
|
</p>
|
|
<p className="text-sm text-gray-500">
|
|
Ingreso: {internacion.fechaIngresoClinica ? formatDateDDMMYYYY(internacion.fechaIngresoClinica) : 'N/A'}
|
|
</p>
|
|
</div>
|
|
<Badge variant="outline" className="bg-purple-50 text-purple-700 dark:bg-purple-900 dark:text-purple-300 dark:border-purple-700">
|
|
Activa
|
|
</Badge>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Accesos Rápidos */}
|
|
<Card>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-lg flex items-center gap-2">
|
|
<Activity className="h-5 w-5 text-green-600" />
|
|
Accesos Rápidos
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<Button
|
|
variant="outline"
|
|
className="h-auto py-4 flex flex-col items-center gap-2"
|
|
onClick={() => onCambiarVista('pacientes')}
|
|
>
|
|
<Users className="h-6 w-6 text-blue-600" />
|
|
<span className="text-sm">Nuevo Paciente</span>
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
className="h-auto py-4 flex flex-col items-center gap-2"
|
|
onClick={() => onCambiarVista('internaciones')}
|
|
>
|
|
<ClipboardList className="h-6 w-6 text-purple-600" />
|
|
<span className="text-sm">Nueva Internación</span>
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
className="h-auto py-4 flex flex-col items-center gap-2"
|
|
onClick={() => onCambiarVista('laboratorios')}
|
|
>
|
|
<FlaskConical className="h-6 w-6 text-amber-600" />
|
|
<span className="text-sm">Laboratorio</span>
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
className="h-auto py-4 flex flex-col items-center gap-2"
|
|
onClick={() => onCambiarVista('cultivos')}
|
|
>
|
|
<Microscope className="h-6 w-6 text-teal-600" />
|
|
<span className="text-sm">Cultivo</span>
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Cultivos Pendientes */}
|
|
<Card>
|
|
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
|
<CardTitle className="text-lg flex items-center gap-2">
|
|
<Microscope className="h-5 w-5 text-teal-600" />
|
|
Cultivos Pendientes
|
|
</CardTitle>
|
|
<Button variant="ghost" size="sm" onClick={() => onCambiarVista('cultivos')}>
|
|
Ver todos
|
|
</Button>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{cultivosRecientes.length === 0 ? (
|
|
<div className="text-center py-8 text-gray-400">
|
|
<Microscope className="h-12 w-12 mx-auto mb-2 opacity-50" />
|
|
<p>No hay cultivos pendientes</p>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{cultivosRecientes.map((cultivo) => {
|
|
const paciente = getPacienteById(cultivo.pacienteId);
|
|
return (
|
|
<div key={cultivo.id} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
|
<div>
|
|
<p className="font-medium text-gray-900">
|
|
{paciente ? `${paciente.apellido}, ${paciente.nombre}` : 'Paciente no encontrado'}
|
|
</p>
|
|
<p className="text-sm text-gray-500">
|
|
{cultivo.tipoMuestra} - {cultivo.fechaToma}
|
|
</p>
|
|
</div>
|
|
<Badge className="bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-300">
|
|
<Clock className="h-3 w-3 mr-1" />
|
|
Pendiente
|
|
</Badge>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|