[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: -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>
This commit is contained in:
@ -1,6 +1,66 @@
|
||||
# HANDOFF — Brief 5 phút cho session tiếp theo
|
||||
|
||||
**Last updated:** 2026-05-11 (Session 20 — **🎯 PE Detail UI restructure 3 yêu cầu UX user (4 chunk `9dee00d`→`2bba851`→`f2f01f4`→Docs). FE-only restructure (1 hook BE nhẹ auto-seed Detail). Q1=a giữ Section "Chọn NCC TP" riêng / Q2=a NCC shared + 1 hạng mục demo / Q3=a chỉ hiện NV đã ký / Q4 public luôn (skip dotnet test, vẫn npm build × 2 app mỗi chunk vì rename/remove function). Chunk A: BE CreatePE handler thêm 1 PurchaseEvaluationDetail mặc định (NoiDung=TenGoiThau, ThanhTienNganSach=Budget.TongNganSach||BudgetManualAmount||0) + FE reorder section Hạng mục lên #2. Chunk B: ItemsTab restructure list HangMucCard 1 card / hạng mục với expand panel chứa NCC inline table 8 cột (NCC/Liên hệ/Điều khoản TT/File báo giá/ĐG chưa VAT/ĐG có VAT/Thành tiền/Action). Click cell quote → QuoteDialog reuse. Drop SuppliersTab function ~134 LOC dead code, giữ 2 dialog + SupplierAttachmentsCell. Section 4 NCC tham gia gộp vào Section 2 → 4 section final (Thông tin/Hạng mục nested/Chọn NCC TP thắng thầu/Ý kiến). Chunk C: LevelOpinionsSectionV2 forEach step → 1 StepOpinionsBox (replace grid-cols-2 N approvers). Header "Bước N — Tên" + dept badge + "X/Y đã duyệt" counter. Body filter signed opinions sort levelOrder asc + signedAt asc → StepOpinionEntry per signed (tên + Cấp badge + admin override badge + timestamp + comment). NV chưa duyệt KHÔNG hiển thị. KHÔNG đụng Mig 26 schema. Drop LevelOpinionBox function. 81 test pass unchanged (UAT defer).**)
|
||||
**Last updated:** 2026-05-11 (Session 20 turn 7 — **🎯 Admin Ẩn/Hiện + Đổi tên menu eOffice (Mig 27, 5 chunk: A schema → B BE API → C FE admin page → D FE user filter → E docs). User Q2=b: DisplayLabel CHỈ áp fe-user, admin giữ Label gốc. NEW MenuVisibilityPage trong fe-admin /system/menu-visibility — table inline edit Toggle Eye/EyeOff + Input rename + Lưu/Khôi phục. PATCH /api/menus/{key}. fe-user Layout filter 2 tầng + effectiveLabel. 27 mig, ~142 endpoints, 34 FE pages, +1 menu key MenuVisibility leaf System. 81 test pass unchanged.**)
|
||||
|
||||
## TL;DR Session 20 turn 7 — Admin Ẩn/Hiện + Đổi tên menu eOffice (Mig 27)
|
||||
|
||||
User UAT live yêu cầu thêm tính năng admin quản lý menu eOffice (fe-user) — Ẩn/Hiện + Đổi tên hiển thị. Hỏi xác nhận "chưa có?" → đúng, chưa có. User clarify **Q2=b "edit hiển thị bên ngoài, chỉ của eOffice thôi"** → admin sidebar luôn dùng Label gốc, DisplayLabel CHỈ áp render fe-user.
|
||||
|
||||
### Q&A chốt scope
|
||||
|
||||
- Q1=**a** Global (không per-role — permission matrix đã handle)
|
||||
- Q2=**b** DisplayLabel chỉ fe-user, admin sidebar giữ Label gốc
|
||||
- Q3=**a** Giữ USER_HIDDEN_KEYS hardcode + tầng IsVisible dynamic combine
|
||||
- Q4=ok UAT iteration skip test, vẫn npm build mỗi chunk
|
||||
|
||||
### Chunk A (`2ea2d27`) — Schema + Migration 27
|
||||
|
||||
Domain MenuItem +IsVisible bool=true +DisplayLabel string?(200). EF config HasDefaultValue(true) + HasMaxLength(200). Migration 27 AddVisibilityAndDisplayLabelToMenuItems (2 AddColumn) — 3-file rule. Apply LocalDB Dev + Design qua --connection override.
|
||||
|
||||
### Chunk B (`ef394f8`) — BE API
|
||||
|
||||
DTO MenuNodeDto + MenuItemDto +isVisible +displayLabel. GetMyMenuTreeQueryHandler pass through (KHÔNG filter server-side — 2 FE app tự quyết render). NEW UpdateMenuItemCommand + Validator (Key required + DisplayLabel max 200) + Handler (whitespace → null). MenusController +PATCH /api/menus/{key} [Authorize Policy="Permissions.Update"].
|
||||
|
||||
### Chunk C (`059bfcb`) — FE Admin MenuVisibilityPage
|
||||
|
||||
Domain MenuKeys +MenuVisibility. DbInitializer +leaf System/MenuVisibility (Eye, Order=94). FE Admin: types/menu.ts mirror, lib/menuKeys.ts +const, Layout resolver +/system/menu-visibility, App.tsx +Route. NEW pages/system/MenuVisibilityPage.tsx ~210 LOC:
|
||||
- PageHeader + description nhắc admin sidebar dùng Tên gốc
|
||||
- 4 StatCard (Tổng / Hiển thị / Đã ẩn / Đã đổi tên)
|
||||
- Search box (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 button emerald-Eye / amber-EyeOff / Lưu (khi dirty) + Khôi phục (khi custom)
|
||||
- onSuccess invalidate `['menus','all']` + `['my-menu']` → live update
|
||||
|
||||
### Chunk D (`1ed6530`) — fe-user Layout filter + render
|
||||
|
||||
types/menu.ts mirror. Layout.tsx:
|
||||
```tsx
|
||||
function filterForUser(nodes: MenuNode[]) {
|
||||
// 2 tầng: hardcode USER_HIDDEN_KEYS + dynamic !isVisible
|
||||
return nodes.filter(n => !USER_HIDDEN_KEYS.has(n.key) && n.isVisible !== false)
|
||||
.map(n => ({ ...n, children: filterForUser(n.children) }))
|
||||
}
|
||||
function effectiveLabel(n) { return (n.displayLabel?.trim()) || n.label }
|
||||
```
|
||||
Replace 3 callsite `{node.label}` → `{effectiveLabel(node)}`. **fe-admin Layout KHÔNG đụng** — render Label gốc + show hết menu (Q2=b).
|
||||
|
||||
### Pending S21+ (cumulative carry over)
|
||||
|
||||
1. Test V2 Service wire + Section gộp + B4 silent 403 + Mig 25 PATCH user-selectable
|
||||
2. **Contract V2 wire (Mig 28+29 mirror PE)** — biggest pending
|
||||
3. Phân quyền strict V2 + drop legacy V1 + Mig 15 cleanup
|
||||
4. Test PATCH /api/menus/{key} validate (Mig 27)
|
||||
5. Skill `permission-matrix` thêm section "menu visibility" — defer cron audit 2026-06-01
|
||||
6. UX verify trong UAT: admin ẩn menu cha → children có ẩn theo không (FE filter chỉ check `!n.isVisible` per-node)
|
||||
|
||||
### Hard blockers ops (carry)
|
||||
|
||||
UAT thật / SMTP / Rotate creds / SQL backup schedule / win-acme fix / remove `.huypham.vn`
|
||||
|
||||
## TL;DR Session 20 — PE Detail UI restructure 3 yêu cầu UX user (previous)
|
||||
|
||||
Note: TL;DR Session 20 PE Detail UI giữ nguyên dưới (rule §6.5 KEEP narrative). Session 20 turn 7 này thêm trên cùng (admin menu visibility — chủ đề khác).
|
||||
|
||||
(4 chunk `9dee00d`→`2bba851`→`f2f01f4`→Docs). FE-only restructure (1 hook BE nhẹ auto-seed Detail). Q1=a giữ Section "Chọn NCC TP" riêng / Q2=a NCC shared + 1 hạng mục demo / Q3=a chỉ hiện NV đã ký / Q4 public luôn (skip dotnet test, vẫn npm build × 2 app mỗi chunk vì rename/remove function). Chunk A: BE CreatePE handler thêm 1 PurchaseEvaluationDetail mặc định (NoiDung=TenGoiThau, ThanhTienNganSach=Budget.TongNganSach||BudgetManualAmount||0) + FE reorder section Hạng mục lên #2. Chunk B: ItemsTab restructure list HangMucCard 1 card / hạng mục với expand panel chứa NCC inline table 8 cột (NCC/Liên hệ/Điều khoản TT/File báo giá/ĐG chưa VAT/ĐG có VAT/Thành tiền/Action). Click cell quote → QuoteDialog reuse. Drop SuppliersTab function ~134 LOC dead code, giữ 2 dialog + SupplierAttachmentsCell. Section 4 NCC tham gia gộp vào Section 2 → 4 section final (Thông tin/Hạng mục nested/Chọn NCC TP thắng thầu/Ý kiến). Chunk C: LevelOpinionsSectionV2 forEach step → 1 StepOpinionsBox (replace grid-cols-2 N approvers). Header "Bước N — Tên" + dept badge + "X/Y đã duyệt" counter. Body filter signed opinions sort levelOrder asc + signedAt asc → StepOpinionEntry per signed (tên + Cấp badge + admin override badge + timestamp + comment). NV chưa duyệt KHÔNG hiển thị. KHÔNG đụng Mig 26 schema. Drop LevelOpinionBox function. 81 test pass unchanged (UAT defer).**)
|
||||
|
||||
## TL;DR Session 20 — PE Detail UI restructure 3 yêu cầu user UX
|
||||
|
||||
|
||||
@ -2,7 +2,8 @@
|
||||
|
||||
> **Update rule:** trước khi bắt đầu 1 task → ghi row vào `🔥 In Progress`. Xong → chuyển sang `✅ Recently Done`.
|
||||
|
||||
**Last updated:** 2026-05-11 (Session 20 — **🎯 PE Detail UI restructure 3 yêu cầu user UX. 4 chunk per-commit `9dee00d` → `2bba851` → `f2f01f4` → (current Chunk D Docs).** Q1=a (giữ Section "Chọn NCC TP" riêng), Q2=a "1 hạng mục trước tiên" (NCC shared, demo 1 hạng mục), Q3=a (chỉ hiện NV đã ký), Q4 public luôn (skip dotnet test mỗi chunk theo memory `feedback_uat_skip_verify`, vẫn `npm run build` × 2 app mỗi chunk vì có rename/remove function). **Chunk A (`9dee00d`)**: BE `CreatePurchaseEvaluationCommandHandler` thêm 1 PurchaseEvaluationDetail mặc định khi tạo phiếu — GroupCode="01", GroupName="Hạng mục chính", NoiDung=TenGoiThau, DonGiaNganSach=ThanhTienNganSach=Budget.TongNganSach hoặc BudgetManualAmount fallback 0; Changelog Insert audit. FE reorder PeDetailTabs (mirror 2 app) 1.Thông tin / 2.Hạng mục (lên #2) / 3.Chọn NCC / 4.NCC tham gia / 5.Ý kiến. **Chunk B (`2bba851`)**: ItemsTab restructure thành list `HangMucCard` (1 card / 1 hạng mục, expanded=true mặc định cho 1 hạng mục demo). Header card: GroupCode + NoiDung + 3 stat (KL/ĐG/TT) + NS link Δ nếu có + Pencil/Trash actions + ▼/▶ toggle expand. Expand body: NCC inline table columns NCC / Liên hệ / Điều khoản TT / **File báo giá** / ĐG chưa VAT / ĐG có VAT / Thành tiền / Action. Quote inline click cell → QuoteDialog cũ reuse. Add NCC + Sửa NCC reuse AddSupplierDialog/EditSupplierDialog cũ. Winner ✓ button mỗi NCC row. Drop function `SuppliersTab` (dead code ~134 LOC, replace bằng HangMucCard expand panel). Giữ AddSupplierDialog + EditSupplierDialog + SupplierAttachmentsCell (HangMucCard call lại). Section layout cuối: 1.Thông tin / 2.Hạng mục + Báo giá NCC (nested) / 3.Chọn NCC TP thắng thầu / 4.Ý kiến cấp duyệt — 4 section. **Chunk C (`f2f01f4`)**: Section Ý kiến restructure render layer (KHÔNG đụng Mig 26 schema — vẫn UPSERT 1 row / Level). LevelOpinionsSectionV2 forEach step → 1 `StepOpinionsBox` (replace grid-cols-2 cho N approver). Box header: "Bước N — Tên" + dept badge emerald + "X/Y đã duyệt" counter. Body: filter opinions theo step.order → sort levelOrder asc, signedAt asc → render `StepOpinionEntry` per signed opinion (tên NV + Cấp badge slate + admin override badge amber nếu có + emerald rounded-full timestamp + comment text). NV chưa duyệt KHÔNG hiển thị (Q3=a). Drop function `LevelOpinionBox` (replaced). Mirror fe-admin + fe-user. Verify build pass cả 2 app sau khi catch TS6133 `SuppliersTab` + `SupplierAttachmentsCell` unused (đã giải quyết: drop SuppliersTab, restore SupplierAttachmentsCell vào HangMucCard cột "File báo giá"). 81 test pass (no change — UAT defer)**)
|
||||
**Last updated:** 2026-05-11 (Session 20 turn 7 — **🎯 Admin Ẩn/Hiện + Đổi tên menu eOffice (Mig 27). 5 chunk `2ea2d27`→`ef394f8`→`059bfcb`→`1ed6530`→Chunk E Docs. User Q2=b: DisplayLabel CHỈ áp fe-user, admin sidebar giữ Label gốc. Domain MenuItem +IsVisible(true) +DisplayLabel(200). Mig 27 AddVisibilityAndDisplayLabelToMenuItems. BE PATCH /api/menus/{key} [Authorize Policy=Permissions.Update]. NEW FE-admin MenuVisibilityPage ~210 LOC (table inline edit per-row + Save dirty + Khôi phục mặc định + Toggle Eye/EyeOff + 4 StatCard). fe-user Layout filterForUser 2 tầng (USER_HIDDEN_KEYS hardcode + !isVisible dynamic) + effectiveLabel(displayLabel || label) replace 3 callsite. fe-admin Layout KHÔNG đụng. +1 menu key MenuVisibility "Menu eOffice" leaf System Order=94. 27 mig, 59 tables, ~142 endpoints, 34 FE pages, 81 test pass (Q4 UAT defer).**)
|
||||
**S20 prev:** 2026-05-11 (Session 20 — **🎯 PE Detail UI restructure 3 yêu cầu user UX. 4 chunk per-commit `9dee00d` → `2bba851` → `f2f01f4` → (current Chunk D Docs).** Q1=a (giữ Section "Chọn NCC TP" riêng), Q2=a "1 hạng mục trước tiên" (NCC shared, demo 1 hạng mục), Q3=a (chỉ hiện NV đã ký), Q4 public luôn (skip dotnet test mỗi chunk theo memory `feedback_uat_skip_verify`, vẫn `npm run build` × 2 app mỗi chunk vì có rename/remove function). **Chunk A (`9dee00d`)**: BE `CreatePurchaseEvaluationCommandHandler` thêm 1 PurchaseEvaluationDetail mặc định khi tạo phiếu — GroupCode="01", GroupName="Hạng mục chính", NoiDung=TenGoiThau, DonGiaNganSach=ThanhTienNganSach=Budget.TongNganSach hoặc BudgetManualAmount fallback 0; Changelog Insert audit. FE reorder PeDetailTabs (mirror 2 app) 1.Thông tin / 2.Hạng mục (lên #2) / 3.Chọn NCC / 4.NCC tham gia / 5.Ý kiến. **Chunk B (`2bba851`)**: ItemsTab restructure thành list `HangMucCard` (1 card / 1 hạng mục, expanded=true mặc định cho 1 hạng mục demo). Header card: GroupCode + NoiDung + 3 stat (KL/ĐG/TT) + NS link Δ nếu có + Pencil/Trash actions + ▼/▶ toggle expand. Expand body: NCC inline table columns NCC / Liên hệ / Điều khoản TT / **File báo giá** / ĐG chưa VAT / ĐG có VAT / Thành tiền / Action. Quote inline click cell → QuoteDialog cũ reuse. Add NCC + Sửa NCC reuse AddSupplierDialog/EditSupplierDialog cũ. Winner ✓ button mỗi NCC row. Drop function `SuppliersTab` (dead code ~134 LOC, replace bằng HangMucCard expand panel). Giữ AddSupplierDialog + EditSupplierDialog + SupplierAttachmentsCell (HangMucCard call lại). Section layout cuối: 1.Thông tin / 2.Hạng mục + Báo giá NCC (nested) / 3.Chọn NCC TP thắng thầu / 4.Ý kiến cấp duyệt — 4 section. **Chunk C (`f2f01f4`)**: Section Ý kiến restructure render layer (KHÔNG đụng Mig 26 schema — vẫn UPSERT 1 row / Level). LevelOpinionsSectionV2 forEach step → 1 `StepOpinionsBox` (replace grid-cols-2 cho N approver). Box header: "Bước N — Tên" + dept badge emerald + "X/Y đã duyệt" counter. Body: filter opinions theo step.order → sort levelOrder asc, signedAt asc → render `StepOpinionEntry` per signed opinion (tên NV + Cấp badge slate + admin override badge amber nếu có + emerald rounded-full timestamp + comment text). NV chưa duyệt KHÔNG hiển thị (Q3=a). Drop function `LevelOpinionBox` (replaced). Mirror fe-admin + fe-user. Verify build pass cả 2 app sau khi catch TS6133 `SuppliersTab` + `SupplierAttachmentsCell` unused (đã giải quyết: drop SuppliersTab, restore SupplierAttachmentsCell vào HangMucCard cột "File báo giá"). 81 test pass (no change — UAT defer)**)
|
||||
|
||||
## 📍 Phase hiện tại: **Phase 9 active — UAT V2 testing với user thật** — **59 DB tables (+1 PurchaseEvaluationLevelOpinions Mig 26), 26 migrations (+1 Mig 26), ~141 API endpoints (no new — UPSERT auto qua Service hook không endpoint riêng, Q1=1B), 33 FE pages. 81 unit test pass** (58 Domain + 23 Infra — no change S19, feature UAT defer test theo §7). 44 gotcha. 30 demo user + 1 test user UAT. 6 skill. **5 trạng thái phiếu** (Nháp/Đã gửi duyệt/Trả lại/Từ chối/Đã duyệt). **2 Workflow schemas đồng tồn tại** post-Session 17: (1) Mig 21 `WorkflowDefinition` flat (V1) — pin với PE/Contract cũ + match Dept+PositionLevel. (2) Mig 22-26 `ApprovalWorkflow` (V2) — pin với PE mới + match ApproverUserId 1-1, Steps/Levels group by Order, Bước (Phòng) > Cấp (N NV OR-of-N), Mig 25 +IsUserSelectable admin pin per version, **Mig 26 +PeLevelOpinions sign-off dynamic theo Level**. Service PE branch theo `ApprovalWorkflowId` set or null. Sau UAT chốt → migrate + drop V1 + Contract V2 wire.
|
||||
|
||||
@ -61,6 +62,7 @@
|
||||
|
||||
| Ngày | Ai | Task | Commit |
|
||||
|---|---|---|---|
|
||||
| 2026-05-11 | Claude | **🎯 SESSION 20 turn 7 — Admin Ẩn/Hiện + Đổi tên menu eOffice (Mig 27, 5 chunk `2ea2d27`→`ef394f8`→`059bfcb`→`1ed6530`→Chunk E Docs)** — User UAT yêu cầu "tính năng Ẩn Hiện và Đổi tên hiển thị của các Menu bên ngoài Office, làm trong Trang Admin Page". Hỏi xác nhận "chưa có" — đúng. User clarify Q2=b "edit hiển thị bên ngoài, chỉ của eOffice thôi" → admin sidebar luôn giữ Label gốc, DisplayLabel CHỈ áp fe-user. Q1=a global (không per-role), Q3=a giữ USER_HIDDEN_KEYS hardcode + tầng IsVisible dynamic combine, Q4 UAT skip test. **Chunk A** Domain MenuItem +IsVisible bool=true +DisplayLabel string?(200) + EF config + Migration 27 AddVisibilityAndDisplayLabelToMenuItems (2 AddColumn) — 3-file rule, apply LocalDB _Dev + _Design OK. **Chunk B** BE API: MenuNodeDto + MenuItemDto +isVisible +displayLabel (sau CRUD flags trước Children). GetMyMenuTreeQueryHandler pass through, KHÔNG filter server-side — 2 FE app tự quyết. UpdateMenuItemCommand + Validator + Handler (trim DisplayLabel whitespace → null). MenusController +PATCH /api/menus/{key} [Authorize Policy=Permissions.Update] body {isVisible, displayLabel}. **Chunk C** Domain MenuKeys +MenuVisibility const + All[] + DbInitializer +leaf "Menu eOffice" Icon=Eye Order=94 (Workflows shift 94→95). Manual seed Mig 27 LocalDB _Dev (INSERT MenuItems + Permissions Admin). FE Admin: types/menu.ts +isVisible +displayLabel, lib/menuKeys.ts +MenuVisibility, Layout resolver +/system/menu-visibility, App.tsx +Route. NEW pages/system/MenuVisibilityPage.tsx ~210 LOC: PageHeader + 4 StatCard (Tổng/Hiển thị/Đã ẩn/Đã đổi tên) + Search + Table 5 cột (Key mono + parentKey ↳ / Tên gốc / Input "Tên hiển thị" inline placeholder "Mặc định: {label}" / Toggle button emerald-Eye / amber-EyeOff / Lưu khi dirty + Khôi phục khi custom). PATCH endpoint, invalidate ['menus','all'] + ['my-menu'] trigger live update sidebar. Row hidden bg-amber-50/40 highlight, custom label bg-brand-50/40. **Chunk D** fe-user types/menu.ts mirror. Layout.tsx filterForUser 2 tầng (USER_HIDDEN_KEYS structural + !isVisible dynamic). Helper effectiveLabel(n) = displayLabel?.trim() || label. Replace 3 callsite {node.label} → {effectiveLabel(node)}. USER_FIXED_TOP "__inbox" entry +isVisible:true cho type check pass. **fe-admin Layout KHÔNG đụng** — admin sidebar render Label gốc + show hết menu (user Q2=b). **Chunk E Docs (current)**. **Stats Session 20 turn 7**: 26→27 mig, 59 DB tables (no change), ~141→142 endpoints, 33→34 FE pages, ~60→61 menu key, 81 test pass (Q4 UAT defer), 44 gotcha (no new). Memory entries 14 (no new). | `2ea2d27` (A Mig 27) · `ef394f8` (B BE API) · `059bfcb` (C FE admin) · `1ed6530` (D FE user) · (current E Docs) |
|
||||
| 2026-05-11 | Claude | **🎯 SESSION 20 — PE Detail UI restructure 3 yêu cầu UX (4 chunk: 9dee00d→2bba851→f2f01f4→Chunk D Docs)** — User UAT live phản hồi "Logic OK rồi, điều chỉnh UI Duyệt NCC 1 tý": (1) Hạng mục lên trên + auto-tạo 1 row từ gói thầu, (2) NCC expand dưới hạng mục, (3) Section Ý kiến gộp đồng cấp cùng Phòng. Q&A clarify trước code (4 câu Q1=a/Q2=a "1 hạng mục"/Q3=a "chỉ hiện signed"/Q4 "public luôn demo thôi"). 4 chunk per-commit pattern `feedback_per_chunk_commit`. **Chunk A** BE `CreatePurchaseEvaluationCommandHandler` + INSERT 1 PurchaseEvaluationDetail mặc định + Changelog (GroupCode=01, NoiDung=TenGoiThau, ThanhTienNganSach=Budget.TongNganSach hoặc BudgetManualAmount fallback 0) + FE reorder PeDetailTabs section. **Chunk B** ItemsTab restructure list HangMucCard (1 card / hạng mục, expanded=true default cho demo 1 hạng mục). Header: GroupCode + NoiDung + 3 stat (KL/ĐG/TT) + NS link Δ + Pencil/Trash + ▼/▶ toggle. Expand body: NCC inline table 8 cột (NCC/Liên hệ/Điều khoản TT/File báo giá/ĐG chưa VAT/ĐG có VAT/Thành tiền/Action). Click cell quote → QuoteDialog reuse. Add NCC/Edit NCC reuse 2 dialog cũ. Winner ✓ button per row. Bỏ Section 4 "NCC tham gia" (gộp vào Section 2 nested) → 4 section final. Drop SuppliersTab function ~134 LOC dead code (replace bằng HangMucCard expand). Giữ AddSupplierDialog + EditSupplierDialog + SupplierAttachmentsCell (HangMucCard reuse). **Chunk C** Section Ý kiến gộp đồng cấp cùng Phòng. LevelOpinionsSectionV2 forEach step → 1 `StepOpinionsBox` (replace grid-cols-2 N approvers). Header: "Bước N — Tên" + dept badge emerald + "X/Y đã duyệt" counter. Body: filter opinions theo step.order → sort levelOrder asc + signedAt asc → render `StepOpinionEntry` per signed (tên NV + Cấp badge slate + admin override amber + timestamp emerald rounded-full + comment). NV chưa duyệt KHÔNG hiển thị (Q3=a). KHÔNG đụng Mig 26 schema (vẫn UPSERT 1 row / Level qua Service). Drop LevelOpinionBox function. Mirror fe-admin + fe-user mỗi chunk. **Verify**: dotnet build pass (Chunk A) + npm build × 2 app pass (Chunk B/C — catch TS6133 SuppliersTab unused + SupplierAttachmentsCell unused, fix re-add cột File báo giá vào nested table). **Test skip** Phase 9 UAT iteration (81 test pass unchanged). **Stats unchanged**: 26 mig, 59 DB tables, ~141 endpoint, 33 FE pages, 44 gotcha, 81 test. **Pending S21+**: Test regression B4 silent 403 (HIGH §7), Test V2 Service wire ApproveV2Async + Section gộp (Chunk C), Test Mig 25 PATCH user-selectable, Contract V2 (Mig 27/28 mirror PE), phân quyền strict V2, drop legacy V1 cleanup. | `9dee00d` (A) · `2bba851` (B) · `f2f01f4` (C) · (current D Docs) |
|
||||
| 2026-05-09 | Claude | **🎯 SESSION 19 — PE Section 5 V2 dynamic theo ApprovalWorkflowLevel + Mig 26 (4 commit: 873e7a1 polish 3 button + 77a3058/90baa8e/6e913b3/Chunk D Mig 26)** — User feedback Section 5 hiện CỨNG 4 box (PheDuyet/CCM/MuaHàng/SmPm Mig 15 từ Phase 8) → cần động theo Workflow V2 đã pin: forEach Step (Phòng) → forEach Level (Cấp) → forEach NV → 1 OpinionBox với ý kiến + tên người ý kiến. Bước 1 Phòng A có 2 NV → 2 box ngang hàng. **5 câu chốt spec trước code:** Q1=1B (gắn — Service auto sync khi duyệt, KHÔNG form input rời), Q2=2A+Admin (NV chính chủ + Admin override với SignedByUserId track actual signer), Q3=chuyển V2 hết (phiếu V1 legacy fallback Mig 15 4 box readOnly), Q4=4C+bonus (Phase=DaDuyet/TuChoi khoá; Admin có quyền duyệt thay; comment empty → "(duyệt — không ý kiến)" placeholder), Q5=5A (layout group Step header "Bước N — Phòng X" + grid-cols-2 cho N approvers). **3 chunk per-commit (memory `feedback_per_chunk_commit`):** Chunk A (`77a3058`) Domain entity `PurchaseEvaluationLevelOpinion : AuditableEntity` (PEId+LevelId UNIQUE composite, Comment nvarchar(2000), SignedAt datetime2, SignedByUserId Guid, SignedByFullName nvarchar(200) denorm) + EF config FK Cascade Pe + Restrict Level + ApplicationDbContext + IApplicationDbContext DbSet + **Migration 26** `AddPeLevelOpinionsForV2` (1 CREATE TABLE + 2 FK + 2 index — UNIQUE composite + IX LevelId). 3-file rule. Apply LocalDB SolutionErp_Dev OK. Chunk B (`90baa8e`) Service `PurchaseEvaluationWorkflowService.ApproveV2Async` sau line log approval → UPSERT row PurchaseEvaluationLevelOpinions cho Cấp hiện tại: match level theo `ApproverUserId == actorUserId` (multi-NV cùng Cấp OR-of-N), fallback first khi Admin override (FE detect SignedByUserId !== Level.ApproverUserId hiển thị "Admin duyệt thay"). Reject KHÔNG sync. Empty/whitespace comment → "(duyệt — không ý kiến)" placeholder. Helper `ResolveActorFullNameAsync(actorUserId, isSystem)` lookup denorm SignedByFullName từ Users (fallback "(System)" / "(unknown)"). DTO `PurchaseEvaluationLevelOpinionDto` 15 fields (LevelId/StepOrder/StepName/StepDepartmentId/StepDepartmentName/LevelOrder/LevelName/ApproverUserId/ApproverFullName/Comment/SignedAt/SignedByUserId/SignedByFullName). GET handler `GetPurchaseEvaluationQueryHandler` Include LevelOpinions + helper `BuildLevelOpinionsAsync` JOIN ApprovalWorkflows.Steps.Levels + Departments + Users → denorm DTO list. Empty list cho phiếu V1 / V2 chưa có cấp duyệt → FE fallback. Chunk C (`6e913b3`) FE Section 5 V2 dynamic: type `PeLevelOpinion` + `PeDetailBundle.levelOpinions[]`. Section 5 conditional: `evaluation.approvalWorkflowId` set → `<LevelOpinionsSectionV2/>` (V2 dynamic), else `<DepartmentOpinionsSection/>` readOnly fallback (V1 legacy giữ Mig 15 4 box). Component `LevelOpinionsSectionV2` group theo step.order: header "Bước N — <name>" + dept badge emerald + hint "(N người duyệt)" khi totalApprovers > 1; body grid-cols-2 cho `step.levels.flatMap(level => level.approvers.map(approver => <LevelOpinionBox/>))`; lookup opinion theo (stepOrder, levelOrder, approverUserId). `LevelOpinionBox` read-only: title "Cấp N — <ApproverFullName>", badge amber "⚠ Admin <name> duyệt thay" khi override, badge emerald "✓ Đã duyệt", empty "— chưa duyệt" italic gray, footer timestamp signedAt format vi-VN. Workspace mode hint giữ amber "Ý kiến + chữ ký auto đồng bộ khi NV duyệt". Mirror fe-admin + fe-user (rule §3.9). Verify: dotnet build pass + dotnet test 81 pass + npm run build × 2 pass · 0 TS error. Chunk D docs (current) STATUS/HANDOFF/migration-todos/CLAUDE.md/schema-diagram §16 mới + session log. Phiếu V1 cũ KHÔNG migrate (giữ Mig 15 readOnly), drop sau UAT confirm. **Stats:** 26 mig (+1), 59 DB tables (+1), ~141 endpoints (no new), 33 FE pages, 81 test pass. Polish 3 button (873e7a1) Hành động đầu Session 19: rút gọn label "✓ Duyệt / ← Trả lại / ✗ Từ chối" + 3 màu khác nhau (emerald/amber/red) + font-bold cho cả 2 app. | `873e7a1` (3 button) · `77a3058` (Chunk A Mig 26) · `90baa8e` (Chunk B Service+DTO+GET) · `6e913b3` (Chunk C FE) · (current Chunk D Docs) |
|
||||
| 2026-05-08 19:45 | Claude | **🎯 SESSION 18 WRAP-UP — PE V2 polish + Clone B (DuyetNccPhuongAn) + 4 bug fix UAT + Mig 25 IsUserSelectable (7 commit `aaa1c6c` → `32a8d4d`)** — User UAT live tiếp Session 17, request chuỗi polish nhỏ + clone V2 cho type B. Áp memory `feedback_uat_skip_verify` (skip dotnet test mỗi chunk, push ngay) + lesson rename/remove → bắt buộc `npm run build`. **B1 (`aaa1c6c`)** Pe Duyệt (`?pendingMe=1`): bỏ dropdown "Tất cả trạng thái" + filter cứng client-side `getPeDisplayStatus === DaGuiDuyet` (loại Nháp/Trả lại/Đã duyệt/Từ chối). Hint amber "Lọc cố định: Đã gửi duyệt". Header count dùng `rows.length` (inbox không paged). Workaround BE /inbox loose UAT trả phiếu Nháp. Mirror fe-admin + fe-user. **B2 (`917446d`)** PeDetailTabs HistoryTab filter chỉ events Trả lại/Gửi duyệt lại: workflow transition về TraLai (phaseAtChange=98) + transition từ TraLai (summary chứa "TraLai →") + sửa nội dung khi phaseAtChange=TraLai. BE giữ audit data đầy đủ, chỉ FE filter (reversible). Empty state "Chưa có lịch sử trả lại / gửi duyệt lại". Mirror cả 2 app. **B3 (`937eb24`) Clone V2 cho B (DuyetNccPhuongAn)** — User chốt "Quy trình chọn thầu phụ - NCC → Duyệt NCC đúng. Clone toàn bộ updates sang Duyệt NCC và Giải pháp". Audit phát hiện 80% chung qua `ApplicableType` discriminator → chỉ thêm 3 file ~60 LOC: (a) `MenuKeys.cs` +const `ApprovalWorkflowDuyetNccPhuongAnV2` + add vào `All[]`. (b) `DbInitializer.SeedMenusAsync` +leaf "Duyệt NCC và Giải pháp (Mới)" dưới root ApprovalWorkflowsV2 + new method `SeedSampleApprovalWorkflowsV2Async` seed `QT-DN-PA-V2-001 v01` (1 Bước Phòng CCM × 1 Cấp NV test, idempotent). (c) `fe-admin/lib/menuKeys.ts` +`AwV2_DuyetNccPhuongAn`. KHÔNG migration / Service / Designer page (Layout regex `^AwV2_(.+)$` đã match dynamic, ApprovalWorkflowsV2Page có `TYPE_CODE_TO_INT` cả 3 type). Rút memory `feedback_audit_reuse_before_clone.md`. **B4 (`f77ea38`) Fix permission silent 403** — Drafter `nv.test` Workspace dropdown empty mặc dù seed OK. Root: class-level `[Authorize(Policy = "Workflows.Read")]` → non-admin 403, TanStack Query catch silent → UI empty không warning. Fix: class-level `[Authorize]` only (any authenticated). GET = list workflow read-only không nhạy cảm; POST + DELETE giữ `Workflows.Create` admin-only. Pattern reusable cho Contract V2 sau. **B5 (`a9c0857`) Fix sidebar highlight queryMatches** — Click phiếu trong leaf "Danh sách" → URL `?type=1&id=abc` → menu mất highlight (gotcha #34 cũ tái phát). Root: queryMatches exact-set equality {type} vs {type, id} length mismatch. Fix: `TRANSIENT_QUERY_KEYS = {id, q, editHeader, page, phase, awId}` strip trước compare. Edge case verified: Danh sách `?type=1` vs Pending `?type=1&pendingMe=1` distinct (không cross-highlight). Mirror cả 2 app Layout.tsx. **B6 (`2a53107`) Mig 25 + Designer pin toggle + bỏ "(clone)" + Workspace filter** — User feedback Admin Designer: bỏ "(clone)" auto-suffix khi clone version (version đã đủ phân biệt) + thêm pin toggle "Cho user pick lúc create phiếu" (multi-select, độc lập IsActive). Migration 25 `AddIsUserSelectableToApprovalWorkflows`: ALTER ApprovalWorkflows +`IsUserSelectable bit NOT NULL DEFAULT 0` + Sql backfill `UPDATE WHERE IsActive=1 SET 1` (giữ behavior cũ active workflow vẫn pickable). Domain ApprovalWorkflow +property. DTO AwDefinitionDto +field. CreateAwDefinitionCommand set default `true` cho version mới (mirror IsActive). New `SetAwUserSelectableCommand` + Handler. API `PATCH /api/approval-workflows-v2/{id}/user-selectable` policy `Workflows.Create`. DbInitializer SeedSampleApprovalWorkflowsV2Async +`IsUserSelectable=true`. FE Designer: `DefinitionDto` +field; badge amber "📌 Cho user chọn"; button "Ghim cho user / Bỏ ghim" + mutation `toggleSelectable`. Designer `name = cloneFrom.name` (bỏ ` (clone)` suffix). Workspace fetch filter `w.isUserSelectable === true` (cả fe-admin + fe-user). **B7 (`32a8d4d`)** Cleanup orphan `.claude.zip + docs.zip` từ harness session start, +`*.zip` rule .gitignore. **Cumulative Session 18:** 25 mig (+1), 58 tables (no new), ~141 endpoints (+1), 33 FE pages, **81 test pass** (no change — feature mới UAT defer test theo §7), 44 gotcha (+1 silent 403). Memory +1 entry. **Pending Session 19+:** Contract V2 wire (Mig 26 mirror PE), phân quyền strict V2, drop legacy V1 cleanup. | `aaa1c6c` (B1) · `917446d` (B2) · `937eb24` (B3) · `f77ea38` (B4) · `a9c0857` (B5) · `2a53107` (B6) · `32a8d4d` (B7) |
|
||||
|
||||
@ -157,6 +157,28 @@ Session log: `2026-04-28-chot-session-4-budget.md`.
|
||||
|
||||
## 📝 Phase 9 — UAT + Ops + carry over (Session 6+ active)
|
||||
|
||||
### ✅ Session 20 turn 7 done (2026-05-11) — Admin Ẩn/Hiện + Đổi tên menu eOffice (Mig 27, 5 chunk `2ea2d27` → `ef394f8` → `059bfcb` → `1ed6530` → Chunk E Docs)
|
||||
|
||||
User UAT yêu cầu admin quản lý menu eOffice (fe-user) — Ẩn/Hiện + Đổi tên. Confirm "chưa có" → tạo mới. User Q2=b clarify quan trọng: DisplayLabel CHỈ áp fe-user, admin sidebar giữ Label gốc.
|
||||
|
||||
- [x] **Chunk A (`2ea2d27`) Schema + Mig 27** — Domain MenuItem +IsVisible bool=true +DisplayLabel string?(200). EF config HasDefaultValue + HasMaxLength. Migration 27 `AddVisibilityAndDisplayLabelToMenuItems` (2 AddColumn) — 3-file rule. Apply LocalDB `_Dev` + `_Design` qua --connection override (memory `feedback_designtime_runtime_db`).
|
||||
|
||||
- [x] **Chunk B (`ef394f8`) BE API** — DTO MenuNodeDto + MenuItemDto +isVisible +displayLabel. GetMyMenuTreeQueryHandler pass through (KHÔNG filter server-side, 2 FE tự quyết). NEW UpdateMenuItemCommand + Validator + Handler (whitespace → null). MenusController +PATCH /api/menus/{key} [Authorize Policy="Permissions.Update"] body `{isVisible, displayLabel}`.
|
||||
|
||||
- [x] **Chunk C (`059bfcb`) FE Admin** — Domain MenuKeys +MenuVisibility + All[]. DbInitializer +leaf "Menu eOffice" Icon=Eye Order=94 (Workflows shift 94→95). Manual seed Mig 27 LocalDB Dev (INSERT MenuItems + Permissions Admin). FE Admin types/menu.ts mirror, menuKeys.ts +const, Layout resolver +/system/menu-visibility, App.tsx +Route. NEW pages/system/MenuVisibilityPage.tsx ~210 LOC: PageHeader + 4 StatCard + Search + Table 5 cột (Key mono + parentKey↳ / Tên gốc / Input "Tên hiển thị" inline / Toggle Eye-EyeOff emerald-amber / Save dirty + Khôi phục custom). onSuccess invalidate ['menus','all'] + ['my-menu'] live update sidebar.
|
||||
|
||||
- [x] **Chunk D (`1ed6530`) FE User** — fe-user types/menu.ts mirror. Layout.tsx filterForUser 2 tầng (USER_HIDDEN_KEYS hardcode structural + !isVisible dynamic). Helper effectiveLabel(n) = displayLabel?.trim() || label. Replace 3 callsite {node.label} → {effectiveLabel(node)}. USER_FIXED_TOP "__inbox" +isVisible:true cho type check. **fe-admin Layout KHÔNG đụng** — admin sidebar render Label gốc + show hết menu (user Q2=b).
|
||||
|
||||
- [x] **Chunk E Docs (current)** — STATUS Recently Done top + Last updated S20 turn 7. HANDOFF TL;DR Session 20 turn 7 trên đầu (giữ S20 prev nguyên §6.5). migration-todos done section (file này) + pending S21+. Session log `2026-05-11-1700-menu-visibility-mig27.md`. KHÔNG đụng rules / architecture / PROJECT-MAP / workflow-contract / forms-spec / database-guide / schema-diagram / CLAUDE.md (defer cron audit 2026-06-01).
|
||||
|
||||
**Stats Session 20 turn 7:** 26→**27 mig** (+1 AddVisibilityAndDisplayLabelToMenuItems), 59 DB tables (no change), ~141→**142 endpoints** (+1 PATCH /menus/{key}), 33→**34 FE pages** (+1 MenuVisibilityPage), ~60→**61 menu key** (+1 MenuVisibility), 81 test pass (Q4 UAT defer), 44 gotcha (no new). Memory entries 14 (no new).
|
||||
|
||||
**Defer Session 21+ (mới sau S20 turn 7):**
|
||||
|
||||
- [ ] Test PATCH /api/menus/{key} validate Key required + DisplayLabel trim
|
||||
- [ ] Skill `permission-matrix` cross-ref section "menu visibility" — defer cron audit 2026-06-01
|
||||
- [ ] UX verify trong UAT: admin ẩn menu cha → children có ẩn theo không? (FE filter check per-node `!n.isVisible`, parent vẫn hiện thì children render. Có thể cần propagate hidden tree-level nếu UAT phản hồi)
|
||||
|
||||
### ✅ Session 20 done (2026-05-11) — PE Detail UI restructure 3 yêu cầu UX user (4 chunk `9dee00d` → `2bba851` → `f2f01f4` → Chunk D Docs)
|
||||
|
||||
User UAT live feedback: "Logic khá OK rồi, điều chỉnh UI Duyệt NCC 1 tý". 3 yêu cầu cụ thể chốt qua Q&A 4 câu (Q1=a giữ Section "Chọn NCC TP" / Q2=a NCC shared + 1 hạng mục demo / Q3=a chỉ hiện NV đã ký / Q4 public luôn skip dotnet test). FE-only restructure (1 hook BE nhẹ auto-seed Detail).
|
||||
|
||||
170
docs/changelog/sessions/2026-05-11-1700-menu-visibility-mig27.md
Normal file
170
docs/changelog/sessions/2026-05-11-1700-menu-visibility-mig27.md
Normal file
@ -0,0 +1,170 @@
|
||||
# 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`:**
|
||||
```csharp
|
||||
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):
|
||||
```csharp
|
||||
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:**
|
||||
```csharp
|
||||
[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:
|
||||
```sql
|
||||
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`:**
|
||||
|
||||
```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)
|
||||
Reference in New Issue
Block a user