feat(supply-chain): add running timing department drilldown
This commit is contained in:
parent
d2046589e8
commit
244950180e
@ -123,9 +123,12 @@ type TimingOverview = {
|
||||
runningCount: number;
|
||||
runningOvertimeCount: number;
|
||||
runningNearOvertimeCount: number;
|
||||
runningDepartments: CompletedDepartmentTimingSummary[];
|
||||
runningNearOvertimeRowIds: string[];
|
||||
runningLongestDays: number;
|
||||
runningLongestTask: string;
|
||||
runningLongestTargetDays: number | null;
|
||||
runningLongestRowId: string | null;
|
||||
};
|
||||
|
||||
type CompletedTimingDetailFilter =
|
||||
@ -139,6 +142,20 @@ type CompletedTimingDetailFilter =
|
||||
}
|
||||
| null;
|
||||
|
||||
type RunningTimingDetailFilter =
|
||||
| {
|
||||
kind: "department";
|
||||
key: CompletedDepartmentTimingSummary["key"];
|
||||
}
|
||||
| {
|
||||
kind: "near-overtime";
|
||||
}
|
||||
| {
|
||||
kind: "longest";
|
||||
rowId: string;
|
||||
}
|
||||
| null;
|
||||
|
||||
type BaseAnalysisDatum = (typeof ANALYSIS_DATA)[Exclude<
|
||||
AnalysisView,
|
||||
"overview"
|
||||
@ -1377,9 +1394,15 @@ function buildTimingOverview(
|
||||
runningCount: runningRows.length,
|
||||
runningOvertimeCount: runningOvertimeRows.length,
|
||||
runningNearOvertimeCount: runningNearOvertimeRows.length,
|
||||
runningDepartments: summarizeCompletedDepartmentTimings(
|
||||
runningRows,
|
||||
runningOvertimeRows,
|
||||
),
|
||||
runningNearOvertimeRowIds: runningNearOvertimeRows.map((row) => row.id),
|
||||
runningLongestDays: longestRunningRow?.totalActualDays ?? 0,
|
||||
runningLongestTask: longestRunningRow?.openingCode ?? "暂无进行中任务",
|
||||
runningLongestTargetDays: longestRunningRow?.standardDays ?? null,
|
||||
runningLongestRowId: longestRunningRow?.id ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
@ -2958,6 +2981,8 @@ export default function NewProductManagementDashboard({
|
||||
>(null);
|
||||
const [completedTimingFilter, setCompletedTimingFilter] =
|
||||
useState<CompletedTimingDetailFilter>(null);
|
||||
const [runningTimingFilter, setRunningTimingFilter] =
|
||||
useState<RunningTimingDetailFilter>(null);
|
||||
const periodRange = useMemo(
|
||||
() => getPeriodRange(period, anchorDate),
|
||||
[anchorDate, period],
|
||||
@ -3050,6 +3075,44 @@ export default function NewProductManagementDashboard({
|
||||
: completedTimingFilter?.kind === "max-overtime"
|
||||
? "最大周期超期任务"
|
||||
: undefined;
|
||||
const selectedRunningDepartment = useMemo(
|
||||
() =>
|
||||
runningTimingFilter?.kind === "department"
|
||||
? (timingOverview.runningDepartments.find(
|
||||
(department) => department.key === runningTimingFilter.key,
|
||||
) ?? null)
|
||||
: null,
|
||||
[runningTimingFilter, timingOverview.runningDepartments],
|
||||
);
|
||||
const runningTimingDetailRows = useMemo(() => {
|
||||
if (runningTimingFilter?.kind === "near-overtime") {
|
||||
const rowIds = new Set(timingOverview.runningNearOvertimeRowIds);
|
||||
return timingDetails.running.filter((row) => rowIds.has(row.id));
|
||||
}
|
||||
if (runningTimingFilter?.kind === "longest") {
|
||||
return timingDetails.running.filter(
|
||||
(row) => row.id === runningTimingFilter.rowId,
|
||||
);
|
||||
}
|
||||
if (selectedRunningDepartment) {
|
||||
const rowIds = new Set(selectedRunningDepartment.overtimeRowIds);
|
||||
return timingDetails.running.filter((row) => rowIds.has(row.id));
|
||||
}
|
||||
return timingDetails.running;
|
||||
}, [
|
||||
runningTimingFilter,
|
||||
selectedRunningDepartment,
|
||||
timingDetails.running,
|
||||
timingOverview.runningNearOvertimeRowIds,
|
||||
]);
|
||||
const runningTimingFilterLabel =
|
||||
selectedRunningDepartment !== null
|
||||
? `${selectedRunningDepartment.label}超时流程`
|
||||
: runningTimingFilter?.kind === "near-overtime"
|
||||
? "临近超期任务"
|
||||
: runningTimingFilter?.kind === "longest"
|
||||
? "最长已用时任务"
|
||||
: undefined;
|
||||
const stageData: StageDatum[] = useMemo(
|
||||
() => (dashboard && liveItems ? buildLiveStageData(liveItems) : STAGE_DATA),
|
||||
[dashboard, liveItems],
|
||||
@ -3264,6 +3327,7 @@ export default function NewProductManagementDashboard({
|
||||
aria-expanded={timingDetailMode === "completed"}
|
||||
onClick={() => {
|
||||
setCompletedTimingFilter(null);
|
||||
setRunningTimingFilter(null);
|
||||
setTimingDetailMode((current) =>
|
||||
current === "completed" ? null : "completed",
|
||||
);
|
||||
@ -3506,7 +3570,7 @@ export default function NewProductManagementDashboard({
|
||||
tone="neutral"
|
||||
/>
|
||||
<EfficiencyCard
|
||||
label="已超时任务"
|
||||
label="周期超期任务"
|
||||
value={timingOverview.runningOvertimeCount}
|
||||
suffix="个"
|
||||
benchmark="当前已用时超过该款总标准"
|
||||
@ -3516,6 +3580,60 @@ export default function NewProductManagementDashboard({
|
||||
: "暂无总周期超时"
|
||||
}
|
||||
tone={timingOverview.runningOvertimeCount ? "danger" : "good"}
|
||||
departmentItems={timingOverview.runningDepartments.map(
|
||||
(department) => ({
|
||||
label: department.label,
|
||||
value: `${department.overtimeCount}个`,
|
||||
tooltipTitle: `${department.label}超时流程:${department.overtimeCount} 个`,
|
||||
tooltipDetail: `进行中流程中,${department.overtimeParts
|
||||
.map((part) => `${part.label} ${part.count} 个`)
|
||||
.join(";")};同一流程不重复计数`,
|
||||
tone:
|
||||
department.overtimeCount > 0
|
||||
? ("danger" as const)
|
||||
: ("good" as const),
|
||||
active:
|
||||
timingDetailMode === "running" &&
|
||||
runningTimingFilter?.kind === "department" &&
|
||||
runningTimingFilter.key === department.key,
|
||||
previewRows: department.overtimeRowIds
|
||||
.slice(0, 4)
|
||||
.flatMap((rowId) => {
|
||||
const row = timingDetails.running.find(
|
||||
(candidate) => candidate.id === rowId,
|
||||
);
|
||||
return row
|
||||
? [
|
||||
{
|
||||
openingCode: row.openingCode,
|
||||
productName: row.productName,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
}),
|
||||
actionLabel: department.overtimeCount
|
||||
? timingDetailMode === "running" &&
|
||||
runningTimingFilter?.kind === "department" &&
|
||||
runningTimingFilter.key === department.key
|
||||
? "已展开,再次点击收起"
|
||||
: `点击查看全部 ${department.overtimeCount} 个流程`
|
||||
: undefined,
|
||||
onSelect: department.overtimeCount
|
||||
? () => {
|
||||
const isActive =
|
||||
timingDetailMode === "running" &&
|
||||
runningTimingFilter?.kind === "department" &&
|
||||
runningTimingFilter.key === department.key;
|
||||
setRunningTimingFilter(
|
||||
isActive
|
||||
? null
|
||||
: { kind: "department", key: department.key },
|
||||
);
|
||||
setTimingDetailMode(isActive ? null : "running");
|
||||
}
|
||||
: undefined,
|
||||
}),
|
||||
)}
|
||||
/>
|
||||
<EfficiencyCard
|
||||
label="临近超时数"
|
||||
@ -3530,6 +3648,51 @@ export default function NewProductManagementDashboard({
|
||||
tone={
|
||||
timingOverview.runningNearOvertimeCount ? "warning" : "good"
|
||||
}
|
||||
hoverDetail={
|
||||
timingOverview.runningNearOvertimeCount > 0
|
||||
? {
|
||||
title: `临近超期任务:${timingOverview.runningNearOvertimeCount} 个`,
|
||||
detail: `距该款总标准剩余 ${NEAR_OVERTIME_WINDOW_DAYS} 天内`,
|
||||
lines: timingOverview.runningNearOvertimeRowIds
|
||||
.slice(0, 4)
|
||||
.flatMap((rowId) => {
|
||||
const row = timingDetails.running.find(
|
||||
(candidate) => candidate.id === rowId,
|
||||
);
|
||||
if (!row) return [];
|
||||
const remaining =
|
||||
(row.standardDays ?? 0) -
|
||||
(row.totalActualDays ?? 0);
|
||||
return [
|
||||
`${row.openingCode} · ${row.productName}`,
|
||||
`已用 ${row.totalActualDays ?? "-"} 天,剩余 ${Math.max(0, remaining)} 天`,
|
||||
];
|
||||
}),
|
||||
actionLabel:
|
||||
timingDetailMode === "running" &&
|
||||
runningTimingFilter?.kind === "near-overtime"
|
||||
? "已展开,再次点击收起"
|
||||
: "点击查看临近超期明细",
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
active={
|
||||
timingDetailMode === "running" &&
|
||||
runningTimingFilter?.kind === "near-overtime"
|
||||
}
|
||||
onClick={
|
||||
timingOverview.runningNearOvertimeCount > 0
|
||||
? () => {
|
||||
const isActive =
|
||||
timingDetailMode === "running" &&
|
||||
runningTimingFilter?.kind === "near-overtime";
|
||||
setRunningTimingFilter(
|
||||
isActive ? null : { kind: "near-overtime" },
|
||||
);
|
||||
setTimingDetailMode(isActive ? null : "running");
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<EfficiencyCard
|
||||
label="最长已用时"
|
||||
@ -3550,13 +3713,64 @@ export default function NewProductManagementDashboard({
|
||||
? "danger"
|
||||
: "warning"
|
||||
}
|
||||
hoverDetail={
|
||||
timingOverview.runningLongestRowId
|
||||
? {
|
||||
title: `最长已用时:${timingOverview.runningLongestDays} 天`,
|
||||
detail: (() => {
|
||||
const row = timingDetails.running.find(
|
||||
(candidate) =>
|
||||
candidate.id ===
|
||||
timingOverview.runningLongestRowId,
|
||||
);
|
||||
return row
|
||||
? `${row.openingCode} · ${row.productName}`
|
||||
: timingOverview.runningLongestTask;
|
||||
})(),
|
||||
lines: [
|
||||
`当前已用时 ${timingOverview.runningLongestDays} 天 / 标准 ${timingOverview.runningLongestTargetDays ?? "-"} 天`,
|
||||
],
|
||||
actionLabel:
|
||||
timingDetailMode === "running" &&
|
||||
runningTimingFilter?.kind === "longest"
|
||||
? "已展开,再次点击收起"
|
||||
: "点击查看最长任务明细",
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
active={
|
||||
timingDetailMode === "running" &&
|
||||
runningTimingFilter?.kind === "longest"
|
||||
}
|
||||
onClick={
|
||||
timingOverview.runningLongestRowId
|
||||
? () => {
|
||||
const isActive =
|
||||
timingDetailMode === "running" &&
|
||||
runningTimingFilter?.kind === "longest";
|
||||
setRunningTimingFilter(
|
||||
isActive
|
||||
? null
|
||||
: {
|
||||
kind: "longest",
|
||||
rowId: timingOverview.runningLongestRowId!,
|
||||
},
|
||||
);
|
||||
setTimingDetailMode(isActive ? null : "running");
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{timingDetailMode === "running" ? (
|
||||
<TimingDetailTable
|
||||
mode="running"
|
||||
rows={timingDetails.running}
|
||||
onClose={() => setTimingDetailMode(null)}
|
||||
rows={runningTimingDetailRows}
|
||||
filterLabel={runningTimingFilterLabel}
|
||||
onClose={() => {
|
||||
setTimingDetailMode(null);
|
||||
setRunningTimingFilter(null);
|
||||
}}
|
||||
onSelectRow={setSelectedRow}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user