diff --git a/fe-user/src/App.tsx b/fe-user/src/App.tsx index 7539cb1..dc8c821 100644 --- a/fe-user/src/App.tsx +++ b/fe-user/src/App.tsx @@ -12,6 +12,7 @@ import { MyContractsPage } from '@/pages/contracts/MyContractsPage' import { PurchaseEvaluationsListPage, PurchaseEvaluationDetailPage } from '@/pages/pe/PurchaseEvaluationsListPage' import { PurchaseEvaluationCreatePage } from '@/pages/pe/PurchaseEvaluationCreatePage' import { PurchaseEvaluationWorkspacePage } from '@/pages/pe/PurchaseEvaluationWorkspacePage' +import { WorkflowMatrixViewPage } from '@/pages/pe/WorkflowMatrixViewPage' import { BudgetsListPage, BudgetDetailPage } from '@/pages/budgets/BudgetsListPage' import { BudgetCreatePage } from '@/pages/budgets/BudgetCreatePage' @@ -34,6 +35,7 @@ function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx b/fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx new file mode 100644 index 0000000..71e134f --- /dev/null +++ b/fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx @@ -0,0 +1,247 @@ +// Plan AA Chunk B (S24, 2026-05-15) — User read-only matrix view của workflow +// V2 đã admin Designer ghim (`IsUserSelectable=true`, Mig 25). Hiển thị tất cả +// version ghim cho ApplicableType (1=DuyetNcc, 2=DuyetNccPhuongAn) dưới dạng +// table 10 cột — Bước/Cấp/NV duyệt + 7 Allow* flag. +// +// URL: /purchase-evaluations/workflow-matrix?type=1|2 +// Mirror layout admin `fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx` +// nhưng drop mutation (Clone/Ghim/Xoá) + render table thay vì ol/li. +import { useQuery } from '@tanstack/react-query' +import { useSearchParams } from 'react-router-dom' +import { Network, CheckCircle2, Pin } from 'lucide-react' +import { PageHeader } from '@/components/PageHeader' +import { api } from '@/lib/api' +import type { AwAdminOverviewDto, AwDefinitionDto, AwLevelDto } from '@/types/approvalWorkflowV2' + +// Mig 23 ApplicableType enum mirror (BE Domain/ApprovalWorkflowsV2) +const TYPE_LABEL: Record = { + 1: 'Duyệt NCC', + 2: 'Duyệt NCC + Giải pháp', +} + +export function WorkflowMatrixViewPage() { + const [searchParams] = useSearchParams() + const rawType = Number(searchParams.get('type')) + const typeInt = rawType === 1 || rawType === 2 ? rawType : 1 + + const { data, isLoading, isError } = useQuery({ + queryKey: ['workflow-matrix', typeInt], + queryFn: async () => { + const res = await api.get('/approval-workflows-v2', { + params: { applicableType: typeInt, isUserSelectable: true }, + }) + return res.data + }, + }) + + // Em chỉ render type được chọn — BE đã filter applicableType. + const summary = data?.types.find(t => t.applicableType === typeInt) + const workflows: AwDefinitionDto[] = summary?.history ?? [] + const typeLabel = summary?.applicableTypeLabel ?? TYPE_LABEL[typeInt] ?? 'Quy trình duyệt' + + return ( +
+ + + Luồng duyệt — {typeLabel} + + } + description="Cấu hình do Admin quản trị. Có thắc mắc liên hệ Admin." + /> + + {isLoading && ( +
+ Đang tải cấu hình... +
+ )} + + {isError && !isLoading && ( +
+ Không thể tải cấu hình quy trình. Thử lại sau. +
+ )} + + {!isLoading && !isError && workflows.length === 0 && ( +
+ Chưa có quy trình nào được Admin ghim cho loại phiếu này. Liên hệ Admin. +
+ )} + + {!isLoading && !isError && workflows.map(wf => ( + + ))} +
+ ) +} + +function WorkflowCard({ wf }: { wf: AwDefinitionDto }) { + // Tính tổng số row table = tổng levels qua all steps. + const totalRows = wf.steps.reduce((sum, s) => sum + s.levels.length, 0) + + return ( +
+
+
+
+

+ {wf.name} +

+ + {wf.code} v{String(wf.version).padStart(2, '0')} + + {wf.isActive && ( + + + Đang dùng + + )} + {wf.isUserSelectable && ( + + + Được ghim + + )} +
+ {wf.description && ( +

{wf.description}

+ )} +
+
+ +
+ {totalRows === 0 ? ( +
+ Quy trình chưa cấu hình bước duyệt nào. +
+ ) : ( +
+ + + + + + + + + + + + + + + + + {wf.steps.map((step, sIdx) => ( + step.levels.length === 0 ? ( + + + + + ) : ( + step.levels.map((level, lIdx) => ( + + {lIdx === 0 && ( + + )} + + + + + + + + + + + )) + ) + ))} + +
Bước (Phòng)CấpNV duyệt + {'↶'} 1 Cấp + + {'↶'} 1 Bước + + {'↶'} Chỉ định + + {'↶'} Drafter + + {'✎'} Section 2 + + {'✎'} Ngân sách + + {'⏩'} Cấp cuối +
+ 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 {level.order} + + + + {level.approverUserName ?? level.approverUserId} + + {level.approverEmail && ( + + {level.approverEmail} + + )} +
+
+ )} +
+ +
+ + Cấu hình do Admin quản trị. Có thắc mắc liên hệ Admin. + +
+
+ ) +} + +function FlagCell({ value }: { value: AwLevelDto[keyof Pick] }) { + return ( + + {value ? ( + {'✓'} + ) : ( + {'—'} + )} + + ) +} diff --git a/fe-user/src/types/approvalWorkflowV2.ts b/fe-user/src/types/approvalWorkflowV2.ts new file mode 100644 index 0000000..4d137c8 --- /dev/null +++ b/fe-user/src/types/approvalWorkflowV2.ts @@ -0,0 +1,56 @@ +// Types mirror BE `AwAdminOverviewDto` (Application/ApprovalWorkflowsV2/ +// ApprovalWorkflowV2AdminFeatures.cs). Chỉ subset cần cho fe-user read-only +// matrix view (Plan AA Chunk B S24). +// +// 7 Allow* flag per slot Level — Mig 29/30/31 cumulative. + +export type AwLevelDto = { + id: string + order: number + name: string | null + approverUserId: string + approverUserName: string | null + approverEmail: string | null + allowReturnOneLevel: boolean + allowReturnOneStep: boolean + allowReturnToAssignee: boolean + allowReturnToDrafter: boolean + allowApproverEditDetails: boolean + allowApproverEditBudget: boolean + allowApproverSkipToFinal: boolean +} + +export type AwStepDto = { + id: string + order: number + name: string + departmentId: string | null + departmentName: string | null + levels: AwLevelDto[] +} + +export type AwDefinitionDto = { + id: string + code: string + version: number + applicableType: number + applicableTypeLabel: string + name: string + description: string | null + isActive: boolean + isUserSelectable: boolean + activatedAt: string | null + createdAt: string + steps: AwStepDto[] +} + +export type AwTypeSummaryDto = { + applicableType: number + applicableTypeLabel: string + active: AwDefinitionDto | null + history: AwDefinitionDto[] +} + +export type AwAdminOverviewDto = { + types: AwTypeSummaryDto[] +}