diff --git a/fe-user/src/components/Layout.tsx b/fe-user/src/components/Layout.tsx index 0a51bcd..cd8e30e 100644 --- a/fe-user/src/components/Layout.tsx +++ b/fe-user/src/components/Layout.tsx @@ -1,7 +1,7 @@ -import { Link, NavLink, Outlet } from 'react-router-dom' +import { createContext, useContext, useEffect, useMemo, useState } from 'react' +import { Link, NavLink, Outlet, useLocation } from 'react-router-dom' import { ChevronDown, Circle, type LucideIcon } from 'lucide-react' import * as Icons from 'lucide-react' -import { useState } from 'react' import { useAuth } from '@/contexts/AuthContext' import { TopBar } from '@/components/TopBar' import type { MenuNode } from '@/types/menu' @@ -23,6 +23,18 @@ const TYPE_CODE_TO_INT: Record = { NguyenTacDv: 7, } +// Reverse lookup int → code (cho auto-expand từ URL ?type=) +const INT_TO_TYPE_CODE: Record = Object.fromEntries( + Object.entries(TYPE_CODE_TO_INT).map(([k, v]) => [v, k]), +) + +// Detect Ct_ group key (no suffix, distinguish from leaf Ct__List/Create/Pending) +const CT_GROUP_PATTERN = /^Ct_([^_]+)$/ +function getCtGroupCode(key: string): string | null { + const m = key.match(CT_GROUP_PATTERN) + return m ? m[1] : null +} + // User-side menu key → route. Differs from admin: Danh sách points to // /my-contracts (user's own drafts), Duyệt to /inbox (pending THEIR approval). function resolvePath(key: string): string | null { @@ -58,6 +70,18 @@ function filterForUser(nodes: MenuNode[]): MenuNode[] { .map(n => ({ ...n, children: filterForUser(n.children) })) } +// Accordion state cho 7 group Ct_ — chỉ 1 expand cùng lúc. Auto-expand +// theo URL `?type=X` để khi navigate giữa các loại HĐ menu tự sync. Group +// non-Ct_ giữ behavior cũ (independent local useState). +type AccordionContextValue = { + expandedCtCode: string | null + setExpandedCtCode: (code: string | null) => void +} +const AccordionContext = createContext({ + expandedCtCode: null, + setExpandedCtCode: () => {}, +}) + function MenuNodeRenderer({ node, depth = 0 }: { node: MenuNode; depth?: number }) { const hasChildren = node.children.length > 0 if (hasChildren) return @@ -65,19 +89,38 @@ function MenuNodeRenderer({ node, depth = 0 }: { node: MenuNode; depth?: number } function MenuGroup({ node, depth }: { node: MenuNode; depth: number }) { - const [open, setOpen] = useState(depth === 0) + const ctCode = getCtGroupCode(node.key) + const accordion = useContext(AccordionContext) + + // Local state cho group thường (top-level "Hợp đồng" mặc định mở) + const [localOpen, setLocalOpen] = useState(depth === 0) + + // Ct_ group: state controlled bởi accordion context + const isAccordion = ctCode != null + const open = isAccordion ? accordion.expandedCtCode === ctCode : localOpen + + function toggle() { + if (isAccordion) { + accordion.setExpandedCtCode(open ? null : ctCode) + } else { + setLocalOpen(o => !o) + } + } + const Icon = getIcon(node.icon) const isTopLevel = depth === 0 return (