Files
teatea-pension/app/(app)/app/settings/status/page.tsx

139 lines
5.9 KiB
TypeScript

import { redirect } from "next/navigation";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { checkDatabaseConnection } from "@/modules/core/server/db";
import { getCurrentAuthContext } from "@/modules/core/server/auth";
import { hasPermission } from "@/modules/core/server/permissions";
import { readData } from "@/modules/core/server/store";
import { IncidentStatusActions } from "@/modules/settings/components/IncidentStatusActions";
import { TableToolbar } from "@/modules/settings/components/TableToolbar";
import { getPage, getPageCount, getSearchParam, paginate, type SearchParams } from "@/modules/settings/lib/pagination";
type StatusPageProps = {
searchParams?: Promise<SearchParams>;
};
export default async function StatusPage({ searchParams }: StatusPageProps): Promise<React.ReactElement> {
const context = await getCurrentAuthContext();
if (!context) {
redirect("/login");
}
if (!hasPermission(context.permissions, "incident:read")) {
redirect("/app/dashboard");
}
const params = (await searchParams) ?? {};
const query = getSearchParam(params, "q");
const page = getPage(params);
const [data, databaseHealth] = await Promise.all([readData(), checkDatabaseConnection()]);
const canManageIncidents = hasPermission(context.permissions, "incident:manage");
const normalizedQuery = query.trim().toLowerCase();
const filteredIncidents = data.incidents.filter((incident) =>
[incident.title, incident.description, incident.source, incident.severity, incident.status]
.join(" ")
.toLowerCase()
.includes(normalizedQuery),
);
const pageCount = getPageCount(filteredIncidents.length);
const visibleIncidents = paginate(filteredIncidents, page);
return (
<div className="flex flex-col gap-5">
<section>
<Badge variant={databaseHealth.ok ? "success" : "danger"}>{databaseHealth.ok ? "Healthy" : "Degraded"}</Badge>
<h2 className="mt-3 text-xl font-semibold"></h2>
<p className="mt-1 text-sm text-muted-foreground">{databaseHealth.reason}</p>
</section>
<section className="grid gap-4 md:grid-cols-4">
<Card>
<CardHeader>
<CardTitle className="text-3xl">{data.organizations.length}</CardTitle>
</CardHeader>
<CardContent className="pt-0 text-sm text-muted-foreground"></CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-3xl">{data.accounts.length}</CardTitle>
</CardHeader>
<CardContent className="pt-0 text-sm text-muted-foreground"></CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-3xl">{data.beds.length}</CardTitle>
</CardHeader>
<CardContent className="pt-0 text-sm text-muted-foreground"></CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-3xl">{data.incidents.length}</CardTitle>
</CardHeader>
<CardContent className="pt-0 text-sm text-muted-foreground"></CardContent>
</Card>
</section>
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent className="p-0">
<TableToolbar
page={Math.min(page, pageCount)}
pageCount={pageCount}
pathname="/app/settings/status"
query={query}
searchPlaceholder="搜索故障标题、来源、状态"
total={filteredIncidents.length}
/>
<div className="overflow-x-auto">
<table className="w-full min-w-[960px] text-sm">
<thead className="bg-secondary text-left text-xs text-muted-foreground">
<tr>
<th className="px-4 py-3 font-medium"></th>
<th className="px-4 py-3 font-medium"></th>
<th className="px-4 py-3 font-medium"></th>
<th className="px-4 py-3 font-medium"></th>
<th className="px-4 py-3 text-right font-medium"></th>
<th className="px-4 py-3 text-right font-medium"></th>
</tr>
</thead>
<tbody className="divide-y bg-card">
{visibleIncidents.map((incident) => (
<tr className="table-row-enter align-top hover:bg-secondary/45" key={incident.id}>
<td className="px-4 py-3">
<p className="font-medium">{incident.title}</p>
<p className="text-xs text-muted-foreground">{incident.description}</p>
</td>
<td className="px-4 py-3">{incident.severity}</td>
<td className="px-4 py-3">{incident.status}</td>
<td className="px-4 py-3 text-muted-foreground">{incident.source}</td>
<td className="px-4 py-3 text-right text-muted-foreground">
{new Date(incident.createdAt).toLocaleString("zh-CN")}
</td>
<td className="px-4 py-3">
{canManageIncidents ? (
<IncidentStatusActions incident={incident} />
) : (
<span className="block text-right text-xs text-muted-foreground"></span>
)}
</td>
</tr>
))}
{visibleIncidents.length === 0 ? (
<tr>
<td className="px-4 py-8 text-center text-muted-foreground" colSpan={6}>
</td>
</tr>
) : null}
</tbody>
</table>
</div>
</CardContent>
</Card>
</div>
);
}