diff --git a/frontend/src/pages/admin/AdminDashboard.tsx b/frontend/src/pages/admin/AdminDashboard.tsx index 754379d..a312c24 100644 --- a/frontend/src/pages/admin/AdminDashboard.tsx +++ b/frontend/src/pages/admin/AdminDashboard.tsx @@ -1,14 +1,14 @@ import { useEffect, useState, useCallback } from "react"; import { Package, ClipboardList, Bell, TrendingUp, AlertTriangle, - RefreshCw, Loader2, AlertCircle, ArrowRight, Clock, MessageCircle, CheckCircle2, + RefreshCw, Loader2, AlertCircle, ArrowRight, Clock, MessageCircle, CheckCircle2, Users, } from "lucide-react"; import { useNavigate } from "react-router-dom"; import { Radio, DatePicker, Drawer, Input } from "antd"; import dayjs, { type Dayjs } from "dayjs"; import { - fetchDashboardStats, fetchWipTasks, fetchDashboardMessages, fetchCompletedTasks, fetchRejectedTasks, - type DashboardStats, type WipTask, type ProductMessageItem, type CompletedTask, type RejectedTask, + fetchDashboardStats, fetchWipTasks, fetchDashboardMessages, fetchCompletedTasks, fetchRejectedTasks, fetchUserOperations, + type DashboardStats, type WipTask, type ProductMessageItem, type CompletedTask, type RejectedTask, type UserOperation, } from "../../services/dashboardApi"; const { RangePicker } = DatePicker; @@ -269,6 +269,10 @@ export default function AdminDashboard() { const [rejectedTasks, setRejectedTasks] = useState([]); const [rejectedLoading, setRejectedLoading] = useState(false); + // 人员操作统计 + const [opStats, setOpStats] = useState([]); + const [opLoading, setOpLoading] = useState(false); + const navigate = useNavigate(); // ── 加载主数据 ── @@ -323,6 +327,18 @@ export default function AdminDashboard() { .finally(() => setRejectedLoading(false)); }; + // 人员操作统计 — 跟随顶部时间筛选 + const loadUserOperations = useCallback(() => { + setOpLoading(true); + const { since, until } = rangeToParams(dateKey, customRange); + fetchUserOperations(since, until) + .then(setOpStats) + .catch(() => setOpStats([])) + .finally(() => setOpLoading(false)); + }, [dateKey, customRange]); + + useEffect(() => { loadUserOperations(); }, [loadUserOperations]); + const onMsgSearch = (value: string) => { setMsgKeyword(value); loadMessages(value); @@ -528,6 +544,50 @@ export default function AdminDashboard() { + {/* ═══ 人员操作统计 ═══ */} +
+
+

+ 👥 人员操作统计 +

+ 接收 / 转交 / 上传备注 · 跟随时间筛选 +
+ {opLoading ? ( +
+ +
+ ) : opStats.length === 0 ? ( +
该时段暂无操作记录
+ ) : ( +
+ + + + + + + + + + + + + {opStats.map((u, idx) => ( + + + + + + + + + ))} + +
#人员接收转交上传备注总次数
{idx + 1}{u.user_name}{u.receive_count}{u.transfer_count}{u.record_count}{u.total}
+
+ )} +
+ {/* ═══ 留言抽屉 ═══ */} 💬 协同留言板 全厂 · {msgTotal} 条} diff --git a/frontend/src/services/dashboardApi.ts b/frontend/src/services/dashboardApi.ts index b7ff6b2..5ae7154 100644 --- a/frontend/src/services/dashboardApi.ts +++ b/frontend/src/services/dashboardApi.ts @@ -54,6 +54,15 @@ export interface RejectedTask { rejected_at: string | null; } +export interface UserOperation { + user_id: string; + user_name: string; + receive_count: number; + transfer_count: number; + record_count: number; + total: number; +} + export interface PersonDevice { product_id: string; serial_number: string; @@ -142,6 +151,14 @@ export async function fetchRejectedTasks(since?: string, until?: string): Promis return data; } +export async function fetchUserOperations(since?: string, until?: string): Promise { + const params: Record = {}; + if (since) params.since = since; + if (until) params.until = until; + const { data } = await api.get("/dashboard/user-operations", { params }); + return data; +} + export async function fetchPeopleWorkload(): Promise { const { data } = await api.get("/dashboard/people-workload"); return data;