Files
solution-erp/docs/changelog/sessions/2026-05-11-1700-menu-visibility-mig27.md
pqhuy1987 aab88621e8 [CLAUDE] Docs: chốt Session 20 turn 7 — Admin Ẩn/Hiện + Đổi tên menu eOffice (Chunk E)
Wrap-up docs cho 4 chunk code đã push:
- 2ea2d27 Chunk A — Mig 27 MenuItem +IsVisible +DisplayLabel + 3-file rule
- ef394f8 Chunk B — BE PATCH /menus/{key} + extend DTOs + UpdateMenuItemCommand
- 059bfcb Chunk C — FE Admin MenuVisibilityPage ~210 LOC + menu key + seed
- 1ed6530 Chunk D — FE User Layout filter !isVisible + render effectiveLabel

Files updated:
- docs/STATUS.md — Last updated + Recently Done row S20 turn 7 trên cùng (giữ
  S20 PE Detail UI row nguyên văn §6.5)
- docs/HANDOFF.md — Last updated + TL;DR Session 20 turn 7 trên đầu + pending
  S21+ + carry blockers (giữ TL;DR Session 20 + 19 nguyên §6.5)
- docs/changelog/migration-todos.md — Phase 9 Session 20 turn 7 done section
  + 3 defer item S21+ (giữ S20 + S19 nguyên §6.5)
- docs/changelog/sessions/2026-05-11-1700-menu-visibility-mig27.md (NEW) —
  session log đầy đủ Q&A + 4 chunk + verify chain + stats

KHÔNG đụng rules / architecture / PROJECT-MAP / workflow-contract / forms-spec
/ database-guide / schema-diagram / CLAUDE.md per §6.5 (drift S20 turn 7
defer cron audit 2026-06-01 — Mig 27 + 1 endpoint + 1 menu key sẽ check
chung lúc đó).

Path filter CI sẽ skip (docs-only commit).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 11:42:12 +07:00

8.2 KiB

Session 20 turn 7 — Admin Ẩn/Hiện + Đổi tên menu eOffice (Mig 27)

Date: 2026-05-11 Commits: 2ea2d27 (A schema) → ef394f8 (B API) → 059bfcb (C admin page) → 1ed6530 (D user layout) → this (E docs)

Bối cảnh

User UAT live yêu cầu thêm tính năng admin "Ẩn/Hiện và Đổi tên hiển thị của các Menu bên ngoài Office" — trang quản lý menu eOffice (fe-user) thực hiện trong Admin Page (fe-admin). User confirm "Hình như chưa có?" — đúng, chưa có.

User clarify quan trọng: "edit hiển thị bên ngoài nhé. Chỉ của eOffice thôi" → Q2=b: DisplayLabel CHỈ áp khi render fe-user. fe-admin sidebar luôn dùng Label gốc (admin dễ debug, không nhầm).

Q&A trước khi code

# Câu hỏi Chốt
Q1 Scope visibility (global vs per-role)? a Global — permission matrix đã handle per-role
Q2 DisplayLabel áp đâu? b Chỉ fe-user, admin sidebar giữ Label gốc
Q3 Init defaults — keep USER_HIDDEN_KEYS hardcode 4 root? a Giữ hardcode + tầng IsVisible dynamic combine
Q4 Verify mode? Phase 9 UAT iteration: skip dotnet test, vẫn npm run build

Chunk A — Schema + Migration 27 (2ea2d27)

Domain MenuItem.cs:

public bool IsVisible { get; set; } = true;
public string? DisplayLabel { get; set; }

EF Configuration: HasDefaultValue(true) + HasMaxLength(200).

Migration 27 AddVisibilityAndDisplayLabelToMenuItems:

  • AddColumn IsVisible bit NOT NULL DEFAULT 1 (backfill existing rows = true)
  • AddColumn DisplayLabel nvarchar(200) NULL
  • 3-file rule

Verify:

  • dotnet build 0 err
  • dotnet ef database update --connection SolutionErp_Dev applied
  • dotnet ef database update SolutionErp_Design applied (catchup Mig 25/26/27)

Chunk B — BE API (ef394f8)

DTOs MenuDtos.cs:

  • MenuNodeDto +IsVisible bool +DisplayLabel string? (sau CRUD flags, trước Children)
  • MenuItemDto +IsVisible +DisplayLabel

GetMyMenuTreeQueryHandler: pass m.IsVisible + m.DisplayLabel vào DTO record. KHÔNG filter IsVisible server-side — 2 FE app tự quyết render gì (fe-admin show all, fe-user filter).

ListMenuItemsQueryHandler: projection thêm 2 field.

NEW UpdateMenuItemCommand + Validator + Handler (PermissionFeatures.cs):

public record UpdateMenuItemCommand(string Key, bool IsVisible, string? DisplayLabel) : IRequest;
// Handler: load by Key, set 2 field, trim DisplayLabel → null nếu whitespace, SaveAsync

MenusController +PATCH endpoint:

[HttpPatch("{key}")]
[Authorize(Policy = "Permissions.Update")]
public async Task<IActionResult> Update(string key, [FromBody] UpdateMenuItemRequest body, CancellationToken ct)
{
    await mediator.Send(new UpdateMenuItemCommand(key, body.IsVisible, body.DisplayLabel), ct);
    return NoContent();
}

Policy reuse Permissions.Update (admin matrix — cùng scope quản trị).

Chunk C — FE Admin MenuVisibilityPage (059bfcb)

Domain MenuKeys.cs: +MenuVisibility = "MenuVisibility" + thêm vào All[].

DbInitializer SeedMenuTreeAsync: +leaf (MenuVisibility, "Menu eOffice", System, 94, "Eye"). Workflows shift Order 94 → 95. Idempotent. Manual seed Mig 27 LocalDB Dev:

INSERT INTO MenuItems ([Key], Label, ParentKey, [Order], Icon, IsVisible, DisplayLabel)
VALUES ('MenuVisibility', N'Menu eOffice', 'System', 94, 'Eye', 1, NULL);

INSERT INTO Permissions (Id, RoleId, MenuKey, CanRead, CanCreate, CanUpdate, CanDelete)
SELECT NEWID(), Id, 'MenuVisibility', 1, 1, 1, 1 FROM Roles WHERE Name = 'Admin';

FE Admin updates:

  • types/menu.ts: MenuItem + MenuNode +isVisible +displayLabel
  • lib/menuKeys.ts: +MenuVisibility const
  • components/Layout.tsx resolver: +MenuVisibility: '/system/menu-visibility'
  • App.tsx: +Route + import MenuVisibilityPage

NEW pages/system/MenuVisibilityPage.tsx (~210 LOC):

Layout pattern reuse PermissionsPage:

  • PageHeader + description nhắc admin sidebar dùng Tên gốc
  • 4 StatCard: Tổng / Hiển thị (eOffice) / Đã ẩn / Đã đổi tên
  • Search input — filter theo key | label | displayLabel
  • Table 5 cột: | Key (mono + parentKey ↳) | Tên gốc | Input "Tên hiển thị" inline (placeholder "Mặc định: {label}") | Toggle Hiển thị/Ẩn (button emerald/amber) | Hành động |
  • Per-row state qua DraftMap — dirty detect = draft[key] !== ev
  • Save button hiện khi dirty: PATCH /menus/{key} body { isVisible, displayLabel } (trim empty → null)
  • "Khôi phục" button khi đã custom (hidden hoặc renamed): force isVisible=true, displayLabel=null
  • onSuccess: invalidate ['menus', 'all'] + ['my-menu'] + clear draft entry → live update sidebar
  • Row hidden: bg-amber-50/40 highlight, input custom label bg-brand-50/40

Chunk D — FE User Layout filter + render (1ed6530)

fe-user/types/menu.ts: mirror fe-admin (MenuItem + MenuNode +isVisible +displayLabel).

fe-user/components/Layout.tsx:

function filterForUser(nodes: MenuNode[]): MenuNode[] {
  // Filter 2 tầng:
  // 1. USER_HIDDEN_KEYS hardcode (Master/System/Forms/Reports — structural never-show)
  // 2. !isVisible dynamic (Mig 27 admin toggle)
  return nodes
    .filter(n => !USER_HIDDEN_KEYS.has(n.key) && n.isVisible !== false)
    .map(n => ({ ...n, children: filterForUser(n.children) }))
}

function effectiveLabel(n: { label: string; displayLabel?: string | null }): string {
  return (n.displayLabel && n.displayLabel.trim()) || n.label
}

Replace 3 callsite {node.label}{effectiveLabel(node)} (Group header / Leaf NavLink / nested NavLink). USER_FIXED_TOP "__inbox" entry +isVisible:true +displayLabel:null cho type check pass.

fe-admin Layout KHÔNG đụng — admin sidebar luôn render Label gốc + show hết menu, kể cả isVisible=false (cho admin biết menu nào đã ẩn để toggle bật lại). Đây chính là điểm khác biệt user yêu cầu Q2=b.

Verify chain mỗi chunk

Chunk BE build FE-admin build FE-user build DB migrate Push
A 0 warn / 0 err (no FE change) (no FE change) Dev + Design applied 2ea2d27
B pass (no FE change) (no FE change) (no DB change) ef394f8
C (no BE change) pass (no FE change) manual SQL seed Mig 27 059bfcb
D (no BE change) pass (re-verify) pass (no DB change) 1ed6530

Stats Session 20 turn 7

Metric Trước Sau Delta
DB tables 59 59 0
Migrations 26 27 +1 (AddVisibilityAndDisplayLabelToMenuItems)
Endpoints ~141 ~142 +1 (PATCH /api/menus/{key})
FE pages 33 34 +1 (MenuVisibilityPage)
Menu keys ~60 ~61 +1 (MenuVisibility)
Unit tests 81 pass 81 pass 0 (Q4 UAT iteration skip)
Gotchas 44 44 0
Commits 5 A/B/C/D + E docs

Cross-ref

  • Memory feedback_audit_reuse_before_clone.md — pattern reuse PermissionsPage UI cho MenuVisibilityPage (table + inline edit + state map)
  • Memory feedback_per_chunk_commit.md — A/B/C/D/E chunk discipline
  • Memory feedback_designtime_runtime_db.md — apply Mig 27 lên cả _Dev (runtime) + _Design (ef tooling) qua --connection override
  • Memory feedback_uat_skip_verify.md — Q4 Phase 9 UAT: skip test, vẫn npm run build
  • Skill permission-matrix — sẽ cross-ref menu visibility section ở audit 2026-06-01

Pending S21+

Carry over từ HANDOFF S19+S20:

  • Test V2 Service wire + Section gộp
  • Test B4 silent 403 (HIGH §7)
  • Contract V2 wire (Mig 28+29 mirror PE pattern) — biggest pending
  • Phân quyền strict V2
  • Drop legacy V1 + Mig 15 cleanup
  • Cron audit 2026-06-01 (skill stale + schema-diagram §16-21)

Mới sau S20 turn 7:

  • Test PATCH /api/menus/{key} validate Key required + DisplayLabel trim
  • Skill permission-matrix thêm section "menu visibility" — defer audit 2026-06-01
  • Test edge case: admin ẩn menu cha → children có ẩn theo không? (hiện FE filter chỉ check !n.isVisible, child có thể vẫn hiện nếu parent vẫn visible — verify UX trong UAT)