chore: initialize Next.js project baseline
This commit is contained in:
166
modules/dashboard/components/DashboardHome.tsx
Normal file
166
modules/dashboard/components/DashboardHome.tsx
Normal file
@@ -0,0 +1,166 @@
|
||||
import {
|
||||
Activity,
|
||||
AlertTriangle,
|
||||
BedDouble,
|
||||
CheckCircle2,
|
||||
Clock3,
|
||||
HeartPulse,
|
||||
ShieldAlert,
|
||||
Users,
|
||||
} from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
|
||||
type Metric = {
|
||||
label: string;
|
||||
value: string;
|
||||
trend: string;
|
||||
icon: LucideIcon;
|
||||
};
|
||||
|
||||
const metrics: Metric[] = [
|
||||
{ label: "在院", value: "126", trend: "+6 本周", icon: Users },
|
||||
{ label: "床位", value: "82%", trend: "28 空闲", icon: BedDouble },
|
||||
{ label: "任务", value: "184", trend: "142 完成", icon: CheckCircle2 },
|
||||
{ label: "异常", value: "9", trend: "3 待复核", icon: HeartPulse },
|
||||
];
|
||||
|
||||
const taskRows = [
|
||||
{ name: "晨间生命体征记录", owner: "护理一组", status: "进行中", due: "09:30" },
|
||||
{ name: "二楼失能老人巡检", owner: "护理二组", status: "待执行", due: "10:00" },
|
||||
{ name: "高血压老人复测", owner: "医护组", status: "需复核", due: "10:30" },
|
||||
{ name: "晚间护理计划生成", owner: "护理主管", status: "待派发", due: "16:00" },
|
||||
];
|
||||
|
||||
const alerts = [
|
||||
{ label: "任务逾期", count: "4", tone: "warning" },
|
||||
{ label: "设备故障", count: "2", tone: "danger" },
|
||||
{ label: "应急处理中", count: "1", tone: "danger" },
|
||||
{ label: "未读通知", count: "18", tone: "success" },
|
||||
] as const;
|
||||
|
||||
export function DashboardHome(): React.ReactElement {
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-7xl flex-col gap-6">
|
||||
<section className="grid gap-5 border-b pb-5 lg:grid-cols-[1.15fr_0.85fr]">
|
||||
<div className="flex flex-col justify-end gap-4">
|
||||
<Badge variant="success">一期</Badge>
|
||||
<div>
|
||||
<h1 className="text-3xl font-semibold tracking-normal md:text-4xl">运营看板</h1>
|
||||
<p className="mt-2 text-sm text-muted-foreground">照护、床位、预警一屏处理。</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button>
|
||||
<Users className="size-4" aria-hidden="true" />
|
||||
老人建档
|
||||
</Button>
|
||||
<Button variant="outline">
|
||||
<ShieldAlert className="size-4" aria-hidden="true" />
|
||||
处理应急事件
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3 rounded-lg border bg-white/72 p-4 shadow-sm">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium">今日闭环</p>
|
||||
</div>
|
||||
<Activity className="size-5 text-primary" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{["档案", "床位", "健康", "护理", "应急"].map((item, index) => (
|
||||
<div key={item} className="flex items-center gap-3">
|
||||
<span className="w-9 text-xs font-medium text-muted-foreground">{item}</span>
|
||||
<div className="h-2 flex-1 rounded-full bg-muted">
|
||||
<div
|
||||
className="h-2 rounded-full bg-primary"
|
||||
style={{ width: `${88 - index * 9}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="w-14 text-right text-xs text-muted-foreground">
|
||||
{88 - index * 9}%
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||
{metrics.map((metric) => (
|
||||
<Card key={metric.label}>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0">
|
||||
<CardDescription>{metric.label}</CardDescription>
|
||||
<metric.icon className="size-4 text-primary" aria-hidden="true" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardTitle className="text-3xl">{metric.value}</CardTitle>
|
||||
<p className="mt-2 text-xs text-muted-foreground">{metric.trend}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</section>
|
||||
|
||||
<section className="grid gap-4 xl:grid-cols-[1.25fr_0.75fr]">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>今日护理任务</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="overflow-hidden rounded-md border">
|
||||
<table className="w-full 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 text-right font-medium">截止</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y bg-card">
|
||||
{taskRows.map((task) => (
|
||||
<tr key={task.name}>
|
||||
<td className="px-4 py-3 font-medium">{task.name}</td>
|
||||
<td className="px-4 py-3 text-muted-foreground">{task.owner}</td>
|
||||
<td className="px-4 py-3">
|
||||
<Badge variant={task.status === "需复核" ? "warning" : "secondary"}>
|
||||
{task.status}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-right text-muted-foreground">{task.due}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>预警与工单</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{alerts.map((alert) => (
|
||||
<div key={alert.label} className="flex items-center justify-between rounded-md border p-3">
|
||||
<div className="flex items-center gap-3">
|
||||
{alert.tone === "danger" ? (
|
||||
<AlertTriangle className="size-4 text-red-600" aria-hidden="true" />
|
||||
) : (
|
||||
<Clock3 className="size-4 text-amber-600" aria-hidden="true" />
|
||||
)}
|
||||
<span className="text-sm font-medium">{alert.label}</span>
|
||||
</div>
|
||||
<Badge variant={alert.tone}>{alert.count}</Badge>
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
1
modules/dashboard/components/index.ts
Normal file
1
modules/dashboard/components/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { DashboardHome } from "./DashboardHome";
|
||||
89
modules/shared/components/ModulePage.tsx
Normal file
89
modules/shared/components/ModulePage.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import { ArrowRight, CheckCircle2 } from "lucide-react";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
|
||||
type ModuleMetric = {
|
||||
label: string;
|
||||
value: string;
|
||||
detail: string;
|
||||
};
|
||||
|
||||
type ModulePageProps = {
|
||||
title: string;
|
||||
eyebrow: string;
|
||||
description: string;
|
||||
icon: LucideIcon;
|
||||
phase: "一期" | "二期预留" | "三期预留";
|
||||
metrics: ModuleMetric[];
|
||||
workflows: string[];
|
||||
};
|
||||
|
||||
export function ModulePage({
|
||||
title,
|
||||
eyebrow,
|
||||
icon: Icon,
|
||||
phase,
|
||||
metrics,
|
||||
workflows,
|
||||
}: ModulePageProps): React.ReactElement {
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-7xl flex-col gap-6">
|
||||
<section className="flex flex-col justify-between gap-4 border-b pb-5 lg:flex-row lg:items-end">
|
||||
<div className="max-w-3xl">
|
||||
<Badge variant={phase === "一期" ? "success" : "secondary"}>{phase}</Badge>
|
||||
<div className="mt-4 flex items-center gap-3">
|
||||
<span className="inline-flex size-11 items-center justify-center rounded-md bg-secondary text-primary">
|
||||
<Icon className="size-5" aria-hidden="true" />
|
||||
</span>
|
||||
<div>
|
||||
<p className="text-xs font-medium text-muted-foreground">{eyebrow}</p>
|
||||
<h1 className="text-2xl font-semibold tracking-normal md:text-3xl">{title}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline">筛选</Button>
|
||||
<Button>
|
||||
新增记录
|
||||
<ArrowRight className="size-4" aria-hidden="true" />
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="grid gap-4 md:grid-cols-3">
|
||||
{metrics.map((metric) => (
|
||||
<Card key={metric.label}>
|
||||
<CardHeader className="p-4">
|
||||
<CardDescription>{metric.label}</CardDescription>
|
||||
<CardTitle className="text-2xl">{metric.value}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-4 pt-0">
|
||||
<p className="text-xs text-muted-foreground">{metric.detail}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>流转</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ol className="grid gap-3 md:grid-cols-2">
|
||||
{workflows.map((workflow) => (
|
||||
<li key={workflow} className="flex items-start gap-3 rounded-md border bg-secondary/30 p-3">
|
||||
<CheckCircle2 className="mt-0.5 size-4 shrink-0 text-primary" aria-hidden="true" />
|
||||
<span className="text-sm">{workflow}</span>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user