[CLAUDE] Domain+Infra+App+FE-Admin: per-ContractType nested sidebar menu
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m48s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m48s
User request: mỗi loại HĐ có menu riêng với 3 action Danh sách / Thao tác / Duyệt. Sidebar giờ 3-level under "Hợp đồng": Hợp đồng (group, expandable) ├── HĐ Thầu phụ (sub-group) │ ├── Danh sách → /contracts?type=1 │ ├── Thao tác → /contracts/new?type=1 │ └── Duyệt → /contracts?type=1&pendingMe=1 ├── HĐ Giao khoán (sub-group) ├── HĐ NCC / Dịch vụ / Mua bán / Nguyên tắc NCC / Nguyên tắc DV └── ... (7 types × 4 = 28 new menu items) BE: - MenuKeys.cs: ContractTypeCodes array + helpers ContractTypeGroup/ List/Create/Pending → key format Ct_<TypeCode>[_<Action>] - DbInitializer.SeedMenuTreeAsync: loop seeds 28 entries under Contracts - GetMyMenuTreeQuery.BuildChildren: descendants of `Contracts` inherit parent permission (avoid adding 28 rows to Permissions table per role) FE: - Layout.tsx recursive: MenuNodeRenderer dispatches group vs leaf by depth; nested groups collapsed by default (top-level expanded). Deeper levels get smaller padding/text + left border guide. - Pattern-based resolvePath: Ct_<Type>_<Action> → URL with query. - Contract type code → int map (matches Domain ContractType enum). - ContractsListPage reads ?type + ?pendingMe, filters client-side. Header title + description reflect active filter. "← Tất cả loại" quick-reset button. - ContractCreatePage new cho admin (copy từ fe-user), pre-select type từ ?type URL param. - App.tsx route /contracts/new → ContractCreatePage. Pure navigation UX; no new permissions needed. Admin + any role with Contracts.Read see full menu; leaves click-through to filtered views. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -46,19 +46,31 @@ public class GetMyMenuTreeQueryHandler(
|
||||
Update: g.Any(p => p.CanUpdate),
|
||||
Delete: g.Any(p => p.CanDelete)));
|
||||
|
||||
// Build tree
|
||||
List<MenuNodeDto> BuildChildren(string? parentKey) => menus
|
||||
// Build tree. Children of `Contracts` (type submenu) inherit parent's
|
||||
// permission so we don't need per-subitem permission rows. This keeps
|
||||
// the permission matrix clean while allowing deep menu structures for
|
||||
// pure navigation.
|
||||
var (contractsRead, contractsCreate, contractsUpdate, contractsDelete) =
|
||||
resolved.TryGetValue(MenuKeys.Contracts, out var cf) ? cf : (false, false, false, false);
|
||||
|
||||
List<MenuNodeDto> BuildChildren(string? parentKey, bool inheritContractsPerm) => menus
|
||||
.Where(m => m.ParentKey == parentKey)
|
||||
.Select(m =>
|
||||
{
|
||||
var flags = resolved.TryGetValue(m.Key, out var f) ? f : (false, false, false, false);
|
||||
// If this node is a descendant of Contracts and has no explicit
|
||||
// perms, take parent Contracts perms.
|
||||
if (inheritContractsPerm && !resolved.ContainsKey(m.Key))
|
||||
flags = (contractsRead, contractsCreate, contractsUpdate, contractsDelete);
|
||||
|
||||
var childInherits = inheritContractsPerm || m.Key == MenuKeys.Contracts;
|
||||
return new MenuNodeDto(m.Key, m.Label, m.ParentKey, m.Order, m.Icon,
|
||||
flags.Item1, flags.Item2, flags.Item3, flags.Item4,
|
||||
BuildChildren(m.Key));
|
||||
BuildChildren(m.Key, childInherits));
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var tree = BuildChildren(null);
|
||||
var tree = BuildChildren(null, inheritContractsPerm: false);
|
||||
|
||||
// Filter: chỉ trả về node có CanRead=true (hoặc có child CanRead=true)
|
||||
static bool HasAccess(MenuNodeDto n) => n.CanRead || n.Children.Any(HasAccess);
|
||||
|
||||
Reference in New Issue
Block a user