From f212f0436589d8d4c7c245529aa538eeeac00a33 Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Fri, 15 May 2026 01:44:05 +0700 Subject: [PATCH] =?UTF-8?q?[CLAUDE]=20FE-Admin=20FE-User:=20Chunk=20L3=20?= =?UTF-8?q?=E2=80=94=20Fix=20Tr=E1=BA=A3=20l=E1=BA=A1i=20dialog=20default?= =?UTF-8?q?=20mode=20=3D=20first=20available=20F1=20(mode=20=C4=91ang=20g?= =?UTF-8?q?=E1=BB=ADi=20duy=E1=BB=87t)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bro UAT S23 t2 catch screenshot: tick AllowReturnToAssignee + untick AllowReturnToDrafter cho slot Approver → user click "Trả lại" → dialog mở với default state `returnMode=Drafter` (S17 backward compat fallback). Radio Drafter HIDDEN vì allowReturnToDrafter=false → user thấy radio Assignee đã pick + Bùi Lê Thủy Trà từ dropdown → click Xác nhận → BE receive `returnMode: 4` (Drafter từ initial state) → throw "Cấp Approver hiện tại không bật mode 'Drafter'. Liên hệ Admin Designer". Bro intent: "cho duyệt trong muốn cho trả lại trong mode đang gửi duyệt chứ ko phải draft, draft chỉ khi trả lại cho người soạn thôi" — 3 F1 modes (OneLevel/OneStep/ Assignee) là "trả lại trong mode đang gửi duyệt" (Phase=ChoDuyet lùi pointer); Drafter mode = trả về Người soạn (Phase=TraLai), CHỈ default khi không có F1 nào. Fix FE × 2 app PeWorkflowPanel.tsx (mirror rule §3.9): - Import useEffect - useEffect khi target=TraLai → compute first available F1 mode: - allowReturnOneLevel ? OneLevel - : allowReturnOneStep ? OneStep - : allowReturnToAssignee ? Assignee - : Drafter (fallback) - setReturnMode(firstAvailable) → Dialog mở với mode đúng selected → user click Xác nhận → BE receive correct mode → ApplyReturnModeAsync check correct flag → PASS. Pattern lesson saved: dialog initial state phải compute từ permission flags KHÔNG hardcode default — admin có thể disable mọi mode khác Drafter, hoặc ngược lại enable F1 only. Verify: - npm run build × 2 app pass (0 TS err) - Bundle hash rotate × 2 app Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/components/pe/PeWorkflowPanel.tsx | 19 ++++++++++++++++- fe-user/src/components/pe/PeWorkflowPanel.tsx | 21 ++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/fe-admin/src/components/pe/PeWorkflowPanel.tsx b/fe-admin/src/components/pe/PeWorkflowPanel.tsx index 85e0014..614f78a 100644 --- a/fe-admin/src/components/pe/PeWorkflowPanel.tsx +++ b/fe-admin/src/components/pe/PeWorkflowPanel.tsx @@ -2,7 +2,7 @@ // Pulls nextPhases từ BE bundle (single source of truth) → render per-phase // action button. Approvals + History moved here from PeDetailTabs (2 section // dưới cùng) để Panel 2 tập trung hiển thị nội dung phiếu (Info + NCC + Items). -import { useState } from 'react' +import { useEffect, useState } from 'react' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' import { Dialog } from '@/components/ui/Dialog' @@ -46,6 +46,23 @@ export function PeWorkflowPanel({ // Mig 29 (S21 t5) — F1 options per-Level (Cấp Approver hiện tại). const levelOptions = evaluation.currentLevelOptions + + // S23 t2 fix bro UAT: khi admin tick AllowReturnToAssignee/OneLevel/OneStep + // (mode đang gửi duyệt) + UNTICK AllowReturnToDrafter, dialog default mode + // phải là first F1 available — KHÔNG để Drafter (radio hidden) làm initial + // → user click Xác nhận sai mode → BE throw "không bật mode Drafter". + // Drafter chỉ làm fallback khi không có F1 nào enabled. + // Per bro intent: "draft chỉ khi trả lại cho người soạn thôi" — 3 F1 modes + // mới là "trả lại trong mode đang gửi duyệt". + useEffect(() => { + if (target === PurchaseEvaluationPhase.TraLai) { + const firstAvailable = levelOptions?.allowReturnOneLevel ? WorkflowReturnMode.OneLevel + : levelOptions?.allowReturnOneStep ? WorkflowReturnMode.OneStep + : levelOptions?.allowReturnToAssignee ? WorkflowReturnMode.Assignee + : WorkflowReturnMode.Drafter + setReturnMode(firstAvailable) + } + }, [target, levelOptions]) // List approvers đã ký (cho mode Assignee dropdown pick) const signedApprovers = (evaluation.levelOpinions ?? []) .map(o => ({ userId: o.approverUserId, fullName: o.approverFullName ?? 'NV' })) diff --git a/fe-user/src/components/pe/PeWorkflowPanel.tsx b/fe-user/src/components/pe/PeWorkflowPanel.tsx index e724e57..c4153bc 100644 --- a/fe-user/src/components/pe/PeWorkflowPanel.tsx +++ b/fe-user/src/components/pe/PeWorkflowPanel.tsx @@ -2,7 +2,7 @@ // Pulls nextPhases từ BE bundle (single source of truth) → render per-phase // action button. Approvals + History moved here from PeDetailTabs (2 section // dưới cùng) để Panel 2 tập trung hiển thị nội dung phiếu (Info + NCC + Items). -import { useState } from 'react' +import { useEffect, useState } from 'react' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { toast } from 'sonner' import { Dialog } from '@/components/ui/Dialog' @@ -35,6 +35,8 @@ export function PeWorkflowPanel({ const [target, setTarget] = useState(null) const [comment, setComment] = useState('') // Mig 28 (S21 t4) — F1 mode Trả lại. Default Drafter (backward compat S17). + // S23 t2 fix: default sang first available F1 mode (mode đang gửi duyệt) khi + // admin tick — Drafter (= trả về Người soạn) chỉ default khi không có F1 nào. const [returnMode, setReturnMode] = useState(WorkflowReturnMode.Drafter) const [returnTargetUserId, setReturnTargetUserId] = useState(null) // Mig 31 (S23 t1) — F2 Approver duyệt thẳng Cấp cuối. Default false (admin opt-in @@ -47,6 +49,23 @@ export function PeWorkflowPanel({ // Mig 29 (S21 t5) — F1 options per-Level (Cấp Approver hiện tại). Null nếu // V1 legacy hoặc pointer chưa init → fallback chỉ "Trả về Drafter". const levelOptions = evaluation.currentLevelOptions + + // S23 t2 fix bro UAT: khi admin tick AllowReturnToAssignee/OneLevel/OneStep + // (mode đang gửi duyệt) + UNTICK AllowReturnToDrafter, dialog default mode + // phải là first F1 available — KHÔNG để Drafter (radio hidden) làm initial + // → user click Xác nhận sai mode → BE throw "không bật mode Drafter". + // Drafter chỉ làm fallback khi không có F1 nào enabled. + // Per bro intent: "draft chỉ khi trả lại cho người soạn thôi" — 3 F1 modes + // mới là "trả lại trong mode đang gửi duyệt". + useEffect(() => { + if (target === PurchaseEvaluationPhase.TraLai) { + const firstAvailable = levelOptions?.allowReturnOneLevel ? WorkflowReturnMode.OneLevel + : levelOptions?.allowReturnOneStep ? WorkflowReturnMode.OneStep + : levelOptions?.allowReturnToAssignee ? WorkflowReturnMode.Assignee + : WorkflowReturnMode.Drafter + setReturnMode(firstAvailable) + } + }, [target, levelOptions]) // List approvers đã ký (cho mode Assignee dropdown pick) const signedApprovers = (evaluation.levelOpinions ?? []) .map(o => ({ userId: o.approverUserId, fullName: o.approverFullName ?? 'NV' }))