Initial role user app

This commit is contained in:
湛兮
2026-06-02 14:46:39 +08:00
commit 003dc60111
62 changed files with 7835 additions and 0 deletions
+168
View File
@@ -0,0 +1,168 @@
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>
);
}