Files
role-user/src/app/(app)/dashboard/page.tsx
T
2026-06-02 14:46:39 +08:00

169 lines
5.7 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 Link from "next/link";
import type { Route } from "next";
import { Bell, CalendarDays, ChevronRight, ClipboardList } from "lucide-react";
import { EmptyState } from "@/components/empty-state";
import { StatusPill } from "@/components/status-pill";
import { formatDateTime, roleNames } from "@/lib/format";
import { getBootstrapData } from "@/lib/mobile-data";
import type { TaskStatus } from "@/lib/types";
const taskStatusText: Record<TaskStatus, string> = {
pending: "待处理",
in_progress: "处理中",
completed: "已完成",
cancelled: "已取消"
};
const taskStatusTone: Record<TaskStatus, "default" | "success" | "warning" | "danger"> = {
pending: "default",
in_progress: "warning",
completed: "success",
cancelled: "danger"
};
export default async function DashboardPage() {
const data = await getBootstrapData();
const { user } = data;
return (
<div className="page-stack">
<section className="dashboard-hero">
<div className="dashboard-hero__content">
<p className="hero-kicker">{user.storeName || "员工端"}</p>
<h1>{user.displayName}</h1>
<p></p>
</div>
<div className="hero-stat-strip" aria-label="今日概览">
<span>
<strong>{data.pendingTaskCount}</strong>
</span>
<span>
<strong>{data.todayShifts.length}</strong>
</span>
<span>
<strong>{data.unreadAnnouncementCount}</strong>
</span>
</div>
</section>
<section className="identity-card">
<div className="identity-card__media">
<span className="avatar">{user.displayName.slice(0, 1)}</span>
</div>
<div className="identity-card__body">
<h2>{user.displayName}</h2>
<p>{user.storeName || "未绑定门店"}</p>
<div className="pill-row">
<StatusPill tone="success"></StatusPill>
<StatusPill>{roleNames(user.roles)}</StatusPill>
</div>
</div>
</section>
<section className="metric-grid">
<Link className="metric-tile" href="/tasks">
<ClipboardList aria-hidden size={20} />
<span>{data.pendingTaskCount}</span>
<p></p>
</Link>
<Link className="metric-tile" href="/tasks">
<Bell aria-hidden size={20} />
<span>{data.overdueTaskCount}</span>
<p></p>
</Link>
<Link className="metric-tile" href="/announcements">
<Bell aria-hidden size={20} />
<span>{data.unreadAnnouncementCount}</span>
<p></p>
</Link>
</section>
<section className="section-block">
<div className="section-title">
<h2></h2>
<CalendarDays aria-hidden size={20} />
</div>
{data.todayShifts.length > 0 ? (
<div className="list-stack">
{data.todayShifts.map((shift) => (
<article className="list-card" key={shift.id}>
<div>
<h3>{shift.position}</h3>
<p>
{formatDateTime(shift.startAt)} - {formatDateTime(shift.endAt)}
</p>
</div>
<StatusPill tone="success"></StatusPill>
</article>
))}
</div>
) : (
<EmptyState title="今日暂无排班" description="今天没有安排班次,可在排班页查看未来排班。" />
)}
</section>
<section className="section-block">
<div className="section-title">
<h2></h2>
<Link href="/tasks">
<ChevronRight aria-hidden size={16} />
</Link>
</div>
{data.tasks.length > 0 ? (
<div className="list-stack">
{data.tasks.map((task) => {
const href = `/tasks/${task.id}` as Route;
return (
<Link className="list-card" href={href} key={task.id}>
<div>
<h3>{task.title}</h3>
<p>{task.description || `截止:${formatDateTime(task.dueAt)}`}</p>
</div>
<StatusPill tone={taskStatusTone[task.status]}>{taskStatusText[task.status]}</StatusPill>
</Link>
);
})}
</div>
) : (
<EmptyState title="暂无待办任务" description="待处理和处理中任务会显示在这里。" />
)}
</section>
<section className="section-block">
<div className="section-title">
<h2></h2>
<Link href="/announcements">
<ChevronRight aria-hidden size={16} />
</Link>
</div>
{data.latestAnnouncements.length > 0 ? (
<div className="list-stack">
{data.latestAnnouncements.map((item) => {
const href = `/announcements/${item.id}` as Route;
return (
<Link className="list-card" href={href} key={item.id}>
<div>
<h3>{item.title}</h3>
<p>{item.summary || formatDateTime(item.publishedAt)}</p>
</div>
<StatusPill tone={item.read ? "default" : "warning"}>{item.read ? "已读" : "未读"}</StatusPill>
</Link>
);
})}
</div>
) : (
<EmptyState title="暂无公告" description="当前没有面向你的门店公告。" />
)}
</section>
</div>
);
}