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;