Initial commit: Sistema de Gestión Hospitalaria
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
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 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">
|
||||
{/* 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">Dashboard</h1>
|
||||
<p className="text-gray-500">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">
|
||||
<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">
|
||||
outside of active service
|
||||
</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.fechaIngreso}
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant="outline" className="bg-purple-50 text-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">
|
||||
<Clock className="h-3 w-3 mr-1" />
|
||||
Pendiente
|
||||
</Badge>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user