feat(透视表): 工序分布包含在库/完成 + 增加时间筛选

- wip-matrix 去掉状态过滤,全量分布,工序包含「在库」(生产完成)
- 后端加 since/until 按任务接手/创建时间过滤
- 前端 MatrixBoard 加 RangePicker 日期筛选 + 全部清除
This commit is contained in:
2026-08-28 16:32:01 +08:00
parent fdd7465afb
commit 41dd257260
4 changed files with 50 additions and 15 deletions

View File

@ -3,21 +3,29 @@
* Y 轴 = 规格型号X 轴 = 人员 或 工序,单元格 = 该交叉点的在制品设备数
*/
import { useEffect, useMemo, useState } from "react";
import { Radio, Table } from "antd";
import { Radio, Table, DatePicker, Button } from "antd";
import type { Dayjs } from "dayjs";
import { fetchWipMatrix, type WipMatrixRow } from "../services/dashboardApi";
const { RangePicker } = DatePicker;
export default function MatrixBoard() {
const [dimension, setDimension] = useState<"assignee" | "task_name">("assignee");
const [range, setRange] = useState<[Dayjs, Dayjs] | null>(null);
const [data, setData] = useState<WipMatrixRow[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
fetchWipMatrix(dimension)
fetchWipMatrix(
dimension,
range ? range[0].startOf("day").toISOString() : undefined,
range ? range[1].endOf("day").toISOString() : undefined,
)
.then(setData)
.catch(() => setData([]))
.finally(() => setLoading(false));
}, [dimension]);
}, [dimension, range]);
// 🔧 核心:扁平数组 → 动态交叉表规格型号为行、dimension_key 为列)
const { columns, dataSource } = useMemo(() => {
@ -67,12 +75,22 @@ export default function MatrixBoard() {
<div className="mb-4 flex flex-wrap items-center justify-between gap-3">
<div>
<h2 className="text-xl font-bold text-gray-800">📊 WIP </h2>
<p className="mt-1 text-sm text-gray-500"> × {dimension === "assignee" ? "人员" : "工序"} · </p>
<p className="mt-1 text-sm text-gray-500"> × {dimension === "assignee" ? "人员" : "工序"} · /</p>
</div>
<div className="flex items-center gap-2">
<RangePicker
size="small"
value={range as any}
onChange={(dates) => setRange(dates as [Dayjs, Dayjs] | null)}
style={{ width: 240 }}
placeholder={["开始日期", "结束日期"]}
/>
{range && <Button size="small" onClick={() => setRange(null)}></Button>}
<Radio.Group value={dimension} onChange={(e) => setDimension(e.target.value)} optionType="button" buttonStyle="solid">
<Radio.Button value="assignee"></Radio.Button>
<Radio.Button value="task_name"></Radio.Button>
</Radio.Group>
</div>
<Radio.Group value={dimension} onChange={(e) => setDimension(e.target.value)} optionType="button" buttonStyle="solid">
<Radio.Button value="assignee"></Radio.Button>
<Radio.Button value="task_name"></Radio.Button>
</Radio.Group>
</div>
<div className="rounded-xl bg-white p-4 shadow-sm">

View File

@ -184,8 +184,13 @@ export async function fetchOperationDetail(
return data;
}
export async function fetchWipMatrix(dimension: "assignee" | "task_name"): Promise<WipMatrixRow[]> {
const { data } = await api.get<WipMatrixRow[]>("/dashboard/wip-matrix", { params: { dimension } });
export async function fetchWipMatrix(
dimension: "assignee" | "task_name", since?: string, until?: string,
): Promise<WipMatrixRow[]> {
const params: Record<string, string> = { dimension };
if (since) params.since = since;
if (until) params.until = until;
const { data } = await api.get<WipMatrixRow[]>("/dashboard/wip-matrix", { params });
return data;
}