From 1ed6530fdd3b6b6d386602ad8f9a78d871a631a2 Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Mon, 11 May 2026 11:39:43 +0700 Subject: [PATCH] =?UTF-8?q?[CLAUDE]=20FE-User:=20Chunk=20D=20=E2=80=94=20L?= =?UTF-8?q?ayout=20filter=20!isVisible=20+=20render=20displayLabel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Session 20 turn 7 Chunk D. fe-user (eOffice) áp dụng Ẩn/Hiện + Đổi tên menu admin set qua MenuVisibilityPage. fe-user/types/menu.ts: MenuItem + MenuNode +isVisible bool +displayLabel string|null (mirror fe-admin). fe-user/components/Layout.tsx: - filterForUser: filter 2 tầng — USER_HIDDEN_KEYS hardcode (Master/System/ Forms/Reports — structural never-show) + dynamic !isVisible (Mig 27) - Helper effectiveLabel(n) = displayLabel?.trim() || label — admin custom label thắng, fallback gốc - Replace 3 callsite `{node.label}` → `{effectiveLabel(node)}` (Group/Leaf/ NavLink render) - USER_FIXED_TOP entry "__inbox" +isVisible:true +displayLabel:null (giữ type check pass) fe-admin Layout KHÔNG đụng — admin sidebar luôn dùng Label gốc + render hết menu (kể cả isVisible=false), per user Q2=b. Verify: - npm run build × fe-user pass - npm run build × fe-admin pass (no regression) Pending Chunk E: Docs S20 turn 7 + STATUS + HANDOFF Co-Authored-By: Claude Opus 4.7 (1M context) --- fe-user/src/components/Layout.tsx | 18 +++++++++++++----- fe-user/src/types/menu.ts | 4 ++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/fe-user/src/components/Layout.tsx b/fe-user/src/components/Layout.tsx index b51fbae..69311f3 100644 --- a/fe-user/src/components/Layout.tsx +++ b/fe-user/src/components/Layout.tsx @@ -99,11 +99,19 @@ const USER_HIDDEN_KEYS = new Set([ ]) function filterForUser(nodes: MenuNode[]): MenuNode[] { + // Filter 2 tầng: hardcode USER_HIDDEN_KEYS (system-level, structural never-show) + // + dynamic isVisible (Mig 27 admin toggle qua MenuVisibilityPage). isVisible + // mặc định true, admin set false → ẩn khỏi sidebar eOffice. return nodes - .filter(n => !USER_HIDDEN_KEYS.has(n.key)) + .filter(n => !USER_HIDDEN_KEYS.has(n.key) && n.isVisible !== false) .map(n => ({ ...n, children: filterForUser(n.children) })) } +// Mig 27: ưu tiên displayLabel admin custom, fallback label gốc. +function effectiveLabel(n: { label: string; displayLabel?: string | null }): string { + return (n.displayLabel && n.displayLabel.trim()) || n.label +} + // Accordion state cho groups Ct_ (7 HĐ) + Pe_ (2 phiếu) — mỗi // family mutex độc lập. Auto-expand theo URL `?type=X` (Ct_ dùng route // /contracts|/my-contracts|/inbox, Pe_ dùng /purchase-evaluations). Group @@ -174,7 +182,7 @@ function MenuGroup({ node, depth }: { node: MenuNode; depth: number }) { > - {node.label} + {effectiveLabel(node)} @@ -236,7 +244,7 @@ function MenuLeaf({ node, depth }: { node: MenuNode; depth: number }) { )} > - {node.label} + {effectiveLabel(node)} ) } @@ -244,7 +252,7 @@ function MenuLeaf({ node, depth }: { node: MenuNode; depth: number }) { // Static entries prepended to the dynamic menu tree — these are user-app // specific (inbox + quick create) not backed by MenuItems DB rows. const USER_FIXED_TOP: MenuNode[] = [ - { key: '__inbox', label: 'Hộp thư', parentKey: null, order: 0, icon: 'Inbox', canRead: true, canCreate: true, canUpdate: true, canDelete: true, children: [] }, + { key: '__inbox', label: 'Hộp thư', parentKey: null, order: 0, icon: 'Inbox', canRead: true, canCreate: true, canUpdate: true, canDelete: true, isVisible: true, displayLabel: null, children: [] }, ] function staticResolvePath(key: string): string | null { @@ -268,7 +276,7 @@ function StaticLeaf({ node }: { node: MenuNode }) { } > - {node.label} + {effectiveLabel(node)} ) } diff --git a/fe-user/src/types/menu.ts b/fe-user/src/types/menu.ts index d5bed7a..43076f3 100644 --- a/fe-user/src/types/menu.ts +++ b/fe-user/src/types/menu.ts @@ -8,6 +8,8 @@ export type MenuNode = { canCreate: boolean canUpdate: boolean canDelete: boolean + isVisible: boolean + displayLabel: string | null children: MenuNode[] } @@ -17,6 +19,8 @@ export type MenuItem = { parentKey: string | null order: number icon: string | null + isVisible: boolean + displayLabel: string | null } export type Role = {