Wrap-up docs cho 4 chunk code đã push: -2ea2d27Chunk A — Mig 27 MenuItem +IsVisible +DisplayLabel + 3-file rule -ef394f8Chunk B — BE PATCH /menus/{key} + extend DTOs + UpdateMenuItemCommand -059bfcbChunk C — FE Admin MenuVisibilityPage ~210 LOC + menu key + seed -1ed6530Chunk 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>
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 build0 errdotnet ef database update --connection SolutionErp_Devapplieddotnet ef database update SolutionErp_Designapplied (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+displayLabellib/menuKeys.ts: +MenuVisibilityconstcomponents/Layout.tsxresolver: +MenuVisibility: '/system/menu-visibility'App.tsx: +Route + importMenuVisibilityPage
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/40highlight, input custom labelbg-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ẫnnpm 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-matrixthê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)