fix(驳回/返工): 抽屉与卡片数字口径一致,同时展示已驳回+返工任务

- 根因:卡片数字含返工任务(tasks_rework 实时快照),但抽屉只列已驳回 → 点开为空
- get_rejected_tasks 扩展返回两类(kind=rejected 按时间过滤 / kind=rework 实时不过滤)
- 前端抽屉按「已驳回」「返工任务」分组展示,数字与明细一一对应
This commit is contained in:
2026-08-28 11:33:54 +08:00
parent 1e6adeb576
commit 90f5718e68
3 changed files with 147 additions and 68 deletions

View File

@ -191,26 +191,42 @@ 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") : "";
const timeStr = t.rejected_at ? dayjs(t.rejected_at).format("YYYY-MM-DD HH:mm") : "";
const isRework = t.kind === "rework";
return (
<div
onClick={() => 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"
>
<div className="flex items-center gap-2">
<AlertTriangle className="h-4 w-4 shrink-0 text-red-500" />
{isRework ? <RefreshCw className="h-4 w-4 shrink-0 text-orange-500" /> : <AlertTriangle className="h-4 w-4 shrink-0 text-red-500" />}
<span className="text-sm font-semibold text-gray-800 truncate">{t.task_name}</span>
<span className="text-xs text-gray-500">|</span>
<span className="shrink-0 rounded-full bg-red-50 px-2 py-0.5 text-[10px] font-bold text-red-600">已驳回</span>
<span className="text-xs text-gray-500 shrink-0">驳回人: {t.rejected_by}</span>
<span className="ml-auto shrink-0 text-xs text-gray-400">{rejectedTime}</span>
{isRework ? (
<span className="shrink-0 rounded-full bg-orange-50 px-2 py-0.5 text-[10px] font-bold text-orange-600">
{t.status === "WIP" ? "返工中" : t.status === "PENDING" ? "待返工" : "已返工"}
</span>
) : (
<span className="shrink-0 rounded-full bg-red-50 px-2 py-0.5 text-[10px] font-bold text-red-600">已驳回</span>
)}
<span className="text-xs text-gray-500 shrink-0">
{isRework ? `返工人: ${t.rework_assignee}` : `驳回人: ${t.rejected_by}`}
</span>
<span className="ml-auto shrink-0 text-xs text-gray-400">{timeStr}</span>
</div>
<div className="mt-1.5 flex items-center gap-1.5 text-[11px] text-gray-400">
<span className="inline-flex items-center gap-1 rounded bg-orange-50 px-1.5 py-0.5 font-medium text-orange-600">
🔁 返工给: {t.rework_assignee}
</span>
<span className="text-gray-300">|</span>
<span className="truncate text-red-500/80" title={t.reject_reason || ""}>原因: {t.reject_reason || "—"}</span>
{!isRework && (
<>
<span className="inline-flex items-center gap-1 rounded bg-orange-50 px-1.5 py-0.5 font-medium text-orange-600">
🔁 返工给: {t.rework_assignee}
</span>
<span className="text-gray-300">|</span>
<span className="truncate text-red-500/80" title={t.reject_reason || ""}>原因: {t.reject_reason || "—"}</span>
</>
)}
{isRework && (
<span className="truncate text-orange-600/80">🔁 返工任务 · {t.rework_assignee} 负责</span>
)}
</div>
<div className="mt-1.5 flex items-center gap-1.5 text-[11px] text-gray-400">
<span className="font-medium text-gray-500">{t.material_name || "未知设备"}</span>
@ -588,7 +604,7 @@ export default function AdminDashboard() {
{/* ═══ 驳回/返工明细抽屉 ═══ */}
<Drawer
title={<span className="text-base font-bold">🔴 驳回/返工明细 <span className="font-normal text-gray-400">被驳回 {rejectedTasks.length} 条</span></span>}
title={<span className="text-base font-bold">🔴 驳回/返工明细 <span className="font-normal text-gray-400">共 {rejectedTasks.length} 条</span></span>}
open={rejectedDrawerOpen}
onClose={() => setRejectedDrawerOpen(false)}
size="large"
@ -600,11 +616,34 @@ export default function AdminDashboard() {
</div>
) : rejectedTasks.length === 0 ? (
<div className="py-16 text-center text-sm text-gray-400">
该时段暂无驳回记录
该时段暂无驳回/返工记录
</div>
) : (
<div>
{rejectedTasks.map(t => <RejectedRow key={t.task_id} t={t} />)}
<div className="space-y-4">
{(() => {
const rejected = rejectedTasks.filter(t => t.kind === "rejected");
const rework = rejectedTasks.filter(t => t.kind === "rework");
return (
<>
{rejected.length > 0 && (
<div>
<h4 className="mb-2 flex items-center gap-1.5 text-xs font-bold text-red-600">
<AlertTriangle className="h-3.5 w-3.5" />已驳回 · {rejected.length} 条
</h4>
{rejected.map(t => <RejectedRow key={t.task_id} t={t} />)}
</div>
)}
{rework.length > 0 && (
<div>
<h4 className="mb-2 flex items-center gap-1.5 text-xs font-bold text-orange-600">
<RefreshCw className="h-3.5 w-3.5" />返工任务 · {rework.length} 条
</h4>
{rework.map(t => <RejectedRow key={t.task_id} t={t} />)}
</div>
)}
</>
);
})()}
</div>
)}
</Drawer>

View File

@ -41,6 +41,7 @@ export interface CompletedTask {
export interface RejectedTask {
task_id: string;
kind: "rejected" | "rework";
task_name: string;
product_sn: string;
external_serial: string | null;
@ -49,7 +50,8 @@ export interface RejectedTask {
rejected_by: string;
rework_assignee: string;
reject_reason: string | null;
rejected_at: string;
status: string | null;
rejected_at: string | null;
}
export interface PersonDevice {