Files
teatea-pension/app/(app)/app/beds/page.tsx

236 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { redirect } from "next/navigation";
import { BedDouble, DoorOpen, History, Wrench } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Table } from "@/components/ui/table";
import { getCurrentAuthContext } from "@/modules/core/server/auth";
import { hasPermission } from "@/modules/core/server/permissions";
import { readData } from "@/modules/core/server/store";
export default async function BedsPage(): Promise<React.ReactElement> {
const context = await getCurrentAuthContext();
if (!context) {
redirect("/login");
}
if (!hasPermission(context.permissions, "facility:read")) {
redirect("/app/dashboard");
}
const data = await readData();
const organizationId = context.organization?.id;
const rooms = organizationId ? data.rooms.filter((room) => room.organizationId === organizationId) : data.rooms;
const beds = organizationId ? data.beds.filter((bed) => bed.organizationId === organizationId) : data.beds;
const admissions = organizationId
? data.admissions.filter((admission) => admission.organizationId === organizationId)
: data.admissions;
const occupiedBeds = beds.filter((bed) => bed.status === "occupied").length;
const maintenanceBeds = beds.filter((bed) => bed.status === "maintenance").length;
const occupancyRate = beds.length > 0 ? Math.round((occupiedBeds / beds.length) * 100) : 0;
return (
<div className="mx-auto flex w-full max-w-7xl flex-col gap-5">
<section className="flex flex-col gap-4 border-b pb-5 lg:flex-row lg:items-end lg:justify-between">
<div>
<Badge variant="success">Facility + Admission</Badge>
<h1 className="mt-3 text-3xl font-semibold tracking-normal"></h1>
<p className="mt-2 text-sm text-muted-foreground"></p>
</div>
</section>
<section className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0">
<div>
<CardDescription></CardDescription>
<CardTitle className="mt-2 text-3xl">{rooms.length}</CardTitle>
</div>
<DoorOpen className="size-5 text-primary" aria-hidden="true" />
</CardHeader>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0">
<div>
<CardDescription></CardDescription>
<CardTitle className="mt-2 text-3xl">{beds.length}</CardTitle>
</div>
<BedDouble className="size-5 text-primary" aria-hidden="true" />
</CardHeader>
<CardContent className="pt-0">
<p className="text-sm text-muted-foreground"> {occupancyRate}%</p>
</CardContent>
</Card>
<Card className={maintenanceBeds > 0 ? "border-amber-200 bg-amber-50/70" : undefined}>
<CardHeader className="flex flex-row items-center justify-between space-y-0">
<div>
<CardDescription className={maintenanceBeds > 0 ? "text-amber-700" : undefined}></CardDescription>
<CardTitle className={maintenanceBeds > 0 ? "mt-2 text-3xl text-amber-700" : "mt-2 text-3xl"}>
{maintenanceBeds}
</CardTitle>
</div>
<Wrench className={maintenanceBeds > 0 ? "size-5 text-amber-700" : "size-5 text-primary"} aria-hidden="true" />
</CardHeader>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0">
<div>
<CardDescription></CardDescription>
<CardTitle className="mt-2 text-3xl">{admissions.length}</CardTitle>
</div>
<History className="size-5 text-primary" aria-hidden="true" />
</CardHeader>
</Card>
</section>
<section className="grid gap-4 xl:grid-cols-[0.9fr_1.1fr]">
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<div className="overflow-x-auto rounded-md border">
<Table className="w-full min-w-[620px] text-sm">
<Table.Header className="bg-secondary text-left text-xs text-muted-foreground">
<Table.Row>
<Table.Head className="px-4 py-3 font-medium"></Table.Head>
<Table.Head className="px-4 py-3 font-medium"></Table.Head>
<Table.Head className="px-4 py-3 font-medium"></Table.Head>
<Table.Head className="px-4 py-3 font-medium"></Table.Head>
<Table.Head className="px-4 py-3 font-medium"></Table.Head>
</Table.Row>
</Table.Header>
<Table.Body className="divide-y bg-card">
{rooms.map((room) => (
<Table.Row key={room.id}>
<Table.Cell className="px-4 py-3 font-medium">{room.name}</Table.Cell>
<Table.Cell className="px-4 py-3 text-muted-foreground">{room.code}</Table.Cell>
<Table.Cell className="px-4 py-3">{room.capacity}</Table.Cell>
<Table.Cell className="px-4 py-3">
{room.occupiedBedCount}/{room.bedCount}
</Table.Cell>
<Table.Cell className="px-4 py-3">
<Badge variant={room.status === "active" ? "success" : "danger"}>{room.status}</Badge>
</Table.Cell>
</Table.Row>
))}
{rooms.length === 0 ? (
<Table.Row>
<Table.Cell className="px-4 py-8 text-center text-muted-foreground" colSpan={5}>
/api/facilities/rooms
</Table.Cell>
</Table.Row>
) : null}
</Table.Body>
</Table>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<div className="overflow-x-auto rounded-md border">
<Table className="w-full min-w-[700px] text-sm">
<Table.Header className="bg-secondary text-left text-xs text-muted-foreground">
<Table.Row>
<Table.Head className="px-4 py-3 font-medium"></Table.Head>
<Table.Head className="px-4 py-3 font-medium"></Table.Head>
<Table.Head className="px-4 py-3 font-medium"></Table.Head>
<Table.Head className="px-4 py-3 font-medium"></Table.Head>
<Table.Head className="px-4 py-3 font-medium"></Table.Head>
</Table.Row>
</Table.Header>
<Table.Body className="divide-y bg-card">
{beds.map((bed) => (
<Table.Row key={bed.id}>
<Table.Cell className="px-4 py-3">{bed.roomName}</Table.Cell>
<Table.Cell className="px-4 py-3 font-medium">{bed.code}</Table.Cell>
<Table.Cell className="px-4 py-3">
<Badge
variant={
bed.status === "available"
? "success"
: bed.status === "occupied"
? "secondary"
: bed.status === "maintenance"
? "warning"
: "danger"
}
>
{bed.status}
</Badge>
</Table.Cell>
<Table.Cell className="px-4 py-3">{bed.currentElderName ?? "-"}</Table.Cell>
<Table.Cell className="px-4 py-3 text-muted-foreground">{bed.notes || "-"}</Table.Cell>
</Table.Row>
))}
{beds.length === 0 ? (
<Table.Row>
<Table.Cell className="px-4 py-8 text-center text-muted-foreground" colSpan={5}>
/api/facilities/beds
</Table.Cell>
</Table.Row>
) : null}
</Table.Body>
</Table>
</div>
</CardContent>
</Card>
</section>
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<div className="overflow-x-auto rounded-md border">
<Table className="w-full min-w-[820px] text-sm">
<Table.Header className="bg-secondary text-left text-xs text-muted-foreground">
<Table.Row>
<Table.Head className="px-4 py-3 font-medium"></Table.Head>
<Table.Head className="px-4 py-3 font-medium"></Table.Head>
<Table.Head className="px-4 py-3 font-medium"></Table.Head>
<Table.Head className="px-4 py-3 font-medium"></Table.Head>
<Table.Head className="px-4 py-3 font-medium"></Table.Head>
<Table.Head className="px-4 py-3 font-medium"></Table.Head>
</Table.Row>
</Table.Header>
<Table.Body className="divide-y bg-card">
{admissions.map((admission) => (
<Table.Row key={admission.id}>
<Table.Cell className="px-4 py-3 font-medium">{admission.elderName}</Table.Cell>
<Table.Cell className="px-4 py-3">
{admission.roomName}-{admission.bedCode}
</Table.Cell>
<Table.Cell className="px-4 py-3">
<Badge variant={admission.status === "active" ? "success" : "secondary"}>{admission.status}</Badge>
</Table.Cell>
<Table.Cell className="px-4 py-3 text-muted-foreground">
{new Date(admission.admittedAt).toLocaleString("zh-CN")}
</Table.Cell>
<Table.Cell className="px-4 py-3 text-muted-foreground">
{admission.dischargedAt ? new Date(admission.dischargedAt).toLocaleString("zh-CN") : "-"}
</Table.Cell>
<Table.Cell className="px-4 py-3 text-muted-foreground">{admission.notes || "-"}</Table.Cell>
</Table.Row>
))}
{admissions.length === 0 ? (
<Table.Row>
<Table.Cell className="px-4 py-8 text-center text-muted-foreground" colSpan={6}>
</Table.Cell>
</Table.Row>
) : null}
</Table.Body>
</Table>
</div>
</CardContent>
</Card>
</div>
);
}