diff --git a/fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx b/fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx index a04c45b..4eb0338 100644 --- a/fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx +++ b/fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx @@ -2,14 +2,17 @@ // V2 đã admin Designer ghim (`IsUserSelectable=true`, Mig 25). Hiển thị tất cả // version ghim cho ApplicableType (1=DuyetNcc, 2=DuyetNccPhuongAn). // -// [Plan AA redesign S24 t1] Bro UAT request: drop table 11 cột compact symbol → -// switch sang panel-per-NV layout mirror admin Designer read-only. 7 label rõ -// tiếng Việt + color coding 2 layer (Step/Phòng + Cấp). Group levels theo -// `level.order` (OR-of-N approvers cùng Cấp render N panel song song). +// [Plan AA redesign v2 S24 t1] Bro UAT request: dạng TABLE tận dụng full width +// thay vì stack vertical panel-per-NV. Cấu trúc 4 cột: +// Bước (Phòng) | Cấp | NV duyệt | Quyền duyệt (grid 2-col 7 label tiếng Việt) +// rowSpan cho Bước (Step) + Cấp (Level order). Color coding 2 layer: +// - Step bg + headerBg cycle 5 màu (blue/purple/emerald/amber/pink) +// - Cấp badge ring cycle 5 màu (violet/sky/teal/orange/rose) +// Mỗi cell Quyền duyệt = 7 checkbox read-only label nguyên văn admin Designer. // // URL: /purchase-evaluations/workflow-matrix?type=1|2 // Mirror layout admin `fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx` -// line 853-949 (read-only — drop input onChange). +// line 853-949 cho 7 checkbox label (read-only — drop input onChange). import { useQuery } from '@tanstack/react-query' import { useSearchParams } from 'react-router-dom' import { Network, CheckCircle2, Pin } from 'lucide-react' @@ -102,11 +105,51 @@ export function WorkflowMatrixViewPage() { ) } +// Build flat row list per Step với rowSpan metadata cho table layout. +// Mỗi Level row = 1 NV slot (Mig 29 OR-of-N split). Group theo level.order. +type Row = { + level: AwLevelDto + order: number + isFirstInStep: boolean + isFirstInCap: boolean + rowSpanStep: number + rowSpanCap: number +} + +function buildStepRows(step: AwDefinitionDto['steps'][number]): Row[] { + const byOrder = new Map() + for (const lvl of step.levels) { + const arr = byOrder.get(lvl.order) ?? [] + arr.push(lvl) + byOrder.set(lvl.order, arr) + } + const sortedOrders = [...byOrder.keys()].sort((a, b) => a - b) + const totalInStep = step.levels.length + + const rows: Row[] = [] + let stepCounter = 0 + for (const order of sortedOrders) { + const levelsOfOrder = byOrder.get(order)! + levelsOfOrder.forEach((lvl, idx) => { + rows.push({ + level: lvl, + order, + isFirstInStep: stepCounter === 0, + isFirstInCap: idx === 0, + rowSpanStep: totalInStep, + rowSpanCap: levelsOfOrder.length, + }) + stepCounter++ + }) + } + return rows +} + function WorkflowCard({ wf }: { wf: AwDefinitionDto }) { const totalLevels = wf.steps.reduce((sum, s) => sum + s.levels.length, 0) return ( -
+
@@ -135,17 +178,115 @@ function WorkflowCard({ wf }: { wf: AwDefinitionDto }) {
-
- {totalLevels === 0 ? ( + {totalLevels === 0 ? ( +
Quy trình chưa cấu hình bước duyệt nào.
- ) : ( - wf.steps.map((step, sIdx) => ( - - )) - )} -
+
+ ) : ( +
+ + + + + + + + + + + + + + + + + {wf.steps.map((step, sIdx) => { + const stepColor = STEP_PALETTE[sIdx % STEP_PALETTE.length] + const rows = buildStepRows(step) + + if (rows.length === 0) { + return ( + + + + + ) + } + + return rows.map(r => { + const levelColor = LEVEL_PALETTE[(r.order - 1) % LEVEL_PALETTE.length] + return ( + + {r.isFirstInStep && ( + + )} + {r.isFirstInCap && ( + + )} + + + + ) + }) + })} + +
Bước (Phòng)CấpNV duyệtQuyền duyệt
+
+ Bước {sIdx + 1} +
+
+ {step.departmentName ?? '(Không gắn phòng)'} +
+
+ Chưa có cấp duyệt +
+
+ Bước {sIdx + 1} +
+
+ {step.departmentName ?? '(Không gắn phòng)'} +
+
+ + Cấp {r.order} + + {r.rowSpanCap > 1 && ( +
+ {r.rowSpanCap} NV OR +
+ (chỉ cần 1 NV duyệt) +
+ )} +
+
+ {r.level.approverUserName ?? r.level.approverUserId} +
+ {r.level.approverEmail && ( +
+ {r.level.approverEmail} +
+ )} +
+
+ + + + + + + +
+
+
+ )}