diff --git a/frontend/src/pages/admin/AdminDashboard.tsx b/frontend/src/pages/admin/AdminDashboard.tsx index c7901ee..502a2b4 100644 --- a/frontend/src/pages/admin/AdminDashboard.tsx +++ b/frontend/src/pages/admin/AdminDashboard.tsx @@ -7,8 +7,8 @@ import { useNavigate } from "react-router-dom"; import { Radio, DatePicker, Drawer, Input } from "antd"; import dayjs, { type Dayjs } from "dayjs"; import { - fetchDashboardStats, fetchWipTasks, fetchDashboardMessages, fetchCompletedTasks, - type DashboardStats, type WipTask, type ProductMessageItem, type CompletedTask, + fetchDashboardStats, fetchWipTasks, fetchDashboardMessages, fetchCompletedTasks, fetchRejectedTasks, + type DashboardStats, type WipTask, type ProductMessageItem, type CompletedTask, type RejectedTask, } from "../../services/dashboardApi"; const { RangePicker } = DatePicker; @@ -188,6 +188,43 @@ function CompletedRow({ t }: { t: CompletedTask }) { ); } +// ─── 被驳回明细项(卡片式) ────────────────────────────── +function RejectedRow({ t }: { t: RejectedTask }) { + const nav = useNavigate(); + const rejectedTime = t.rejected_at ? dayjs(t.rejected_at).format("YYYY-MM-DD HH:mm") : ""; + return ( +
t.product_sn && nav(`/admin/tasks?sn=${t.product_sn}`)} + className="cursor-pointer rounded-lg border border-gray-100 bg-white px-4 py-3 transition-shadow hover:border-red-200 hover:shadow-md" + > +
+ + {t.task_name} + | + 已驳回 + 驳回人: {t.rejected_by} + {rejectedTime} +
+
+ + 🔁 返工给: {t.rework_assignee} + + | + 原因: {t.reject_reason || "—"} +
+
+ {t.material_name || "未知设备"} + | + {t.spec_model || "无规格"} + | + 序列号: {t.external_serial || "未录入"} + | + 身份证: {t.product_sn} +
+
+ ); +} + // ─── 主组件 ─────────────────────────────────────────────── export default function AdminDashboard() { const [stats, setStats] = useState(null); @@ -211,6 +248,11 @@ export default function AdminDashboard() { const [completedTasks, setCompletedTasks] = useState([]); const [completedLoading, setCompletedLoading] = useState(false); + // 驳回/返工明细抽屉 + const [rejectedDrawerOpen, setRejectedDrawerOpen] = useState(false); + const [rejectedTasks, setRejectedTasks] = useState([]); + const [rejectedLoading, setRejectedLoading] = useState(false); + const navigate = useNavigate(); // ── 加载主数据 ── @@ -255,6 +297,16 @@ export default function AdminDashboard() { .finally(() => setCompletedLoading(false)); }; + const openRejectedDrawer = () => { + setRejectedDrawerOpen(true); + setRejectedLoading(true); + const { since, until } = rangeToParams(dateKey, customRange); + fetchRejectedTasks(since, until) + .then(setRejectedTasks) + .catch(() => setRejectedTasks([])) + .finally(() => setRejectedLoading(false)); + }; + const onMsgSearch = (value: string) => { setMsgKeyword(value); loadMessages(value); @@ -358,10 +410,16 @@ export default function AdminDashboard() {

⚠️ 品质与协同

-
+ {/* 驳回/返工 — 可点击打开明细抽屉 */} +
+

+ 驳回/返工 ↗ +

+ {/* 留言 — 可点击打开抽屉 */}
); } diff --git a/frontend/src/services/dashboardApi.ts b/frontend/src/services/dashboardApi.ts index ae197a0..e56934f 100644 --- a/frontend/src/services/dashboardApi.ts +++ b/frontend/src/services/dashboardApi.ts @@ -39,6 +39,19 @@ export interface CompletedTask { completed_at: string; } +export interface RejectedTask { + task_id: string; + task_name: string; + product_sn: string; + external_serial: string | null; + material_name: string; + spec_model: string; + rejected_by: string; + rework_assignee: string; + reject_reason: string | null; + rejected_at: string; +} + export interface PersonDevice { product_id: string; serial_number: string; @@ -119,6 +132,14 @@ export async function fetchCompletedTasks(since?: string, until?: string): Promi return data; } +export async function fetchRejectedTasks(since?: string, until?: string): Promise { + const params: Record = {}; + if (since) params.since = since; + if (until) params.until = until; + const { data } = await api.get("/dashboard/rejected-tasks", { params }); + return data; +} + export async function fetchPeopleWorkload(): Promise { const { data } = await api.get("/dashboard/people-workload"); return data;