diff --git a/app/(application)/admin/_layout.tsx b/app/(application)/admin/_layout.tsx index 94f1d99..7400232 100644 --- a/app/(application)/admin/_layout.tsx +++ b/app/(application)/admin/_layout.tsx @@ -9,6 +9,7 @@ import { import DrawerAdmin from "@/components/Drawer/DrawerAdmin"; import NavbarMenu from "@/components/Drawer/NavbarMenu"; import NavbarMenu_V2 from "@/components/Drawer/NavbarMenu_V2"; +import NavbarMenu_V3 from "@/components/Drawer/NavbarMenu_V3"; import { AccentColor, MainColor } from "@/constants/color-palet"; import { ICON_SIZE_MEDIUM, @@ -154,7 +155,16 @@ export default function AdminLayout() { onClose={() => setOpenDrawerNavbar(false)} /> */} - setOpenDrawerNavbar(false)} + /> */} + + void; +} + +export default function NavbarMenu_V3({ items, onClose }: NavbarMenuProps) { + const pathname = usePathname(); + const [openKeys, setOpenKeys] = useState([]); + + // Normalisasi path: hapus trailing slash + const normalizePath = (path: string) => path.replace(/\/+$/, ""); + const normalizedPathname = pathname ? normalizePath(pathname) : ""; + + // Auto-open parent menu jika submenu aktif + useEffect(() => { + if (!normalizedPathname || !items || items.length === 0) { + return; + } + + try { + const newOpenKeys: string[] = []; + + // Helper function yang sama dengan di MenuItem + const checkPathMatch = (linkPath: string, detailPattern?: string) => { + const normalizedLink = linkPath.replace(/\/+$/, ""); + + // Exact match + if (normalizedPathname === normalizedLink) return true; + + // Detail pattern match + if (detailPattern) { + const patternRegex = new RegExp( + "^" + detailPattern.replace(/\*/g, "[^/]+") + "(/.*)?$" + ); + if (patternRegex.test(normalizedPathname)) { + return true; + } + } + + // Detail page match (fallback) + if (normalizedPathname.startsWith(normalizedLink + "/")) { + const remainder = normalizedPathname.substring(normalizedLink.length + 1); + const segments = remainder.split("/").filter(s => s.length > 0); + + if (segments.length === 0) return false; + + const commonWords = [ + // Actions + 'detail', 'edit', 'create', 'new', 'add', 'delete', 'view', + + // Status types + 'publish', 'review', 'reject', 'status', 'active', 'inactive', 'pending', + + // General pages + 'category', 'history', 'dashboard', 'index', + + // Event specific + 'type-of-event', 'type-create', 'type-update', + + // Forum specific + 'posting', 'report-posting', 'report-comment', + + // Collaboration + 'group', + + // App Information + 'business-field', 'information-bank', 'sticker', + 'bidang-update', 'sub-bidang-update', + + // Transaction/Finance related + 'transaction-detail', 'transaction', 'payment', + 'disbursement-of-funds', 'detail-disbursement-of-funds', + 'list-disbursement-of-funds', + + // List pages (CRITICAL!) + 'list-of-investor', 'list-of-donatur', 'list-of-participants', + 'list-comment', 'list-report-comment', 'list-report-posting', + + // Input/Form pages + 'reject-input', + + // Category pages + 'category-create', 'category-update' + ]; + + const hasIdSegment = segments.some(segment => { + if (commonWords.includes(segment.toLowerCase())) { + return false; + } + + const isPureNumber = /^\d+$/.test(segment); + const isUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(segment); + const hasNumber = /\d/.test(segment); + const isAlphanumericId = /^[a-z0-9_-]+$/i.test(segment) && segment.length <= 50 && hasNumber; + + return isPureNumber || isUUID || isAlphanumericId; + }); + + return hasIdSegment; + } + + return false; + }; + + // Calculate all potential matches for conflict resolution + const allMatches = items.flatMap(item => { + if (!item.links || item.links.length === 0) return []; + + return item.links + .filter(subItem => checkPathMatch(subItem.link, subItem.detailPattern)) + .map(subItem => ({ + parentLabel: item.label, + subItem, + pathLength: subItem.link.length + })); + }); + + // Find the most specific match for each parent + const uniqueParents = new Map(); + + allMatches.forEach(match => { + const existing = uniqueParents.get(match.parentLabel); + if (!existing || match.pathLength > existing.longestPathLength) { + uniqueParents.set(match.parentLabel, { + parentLabel: match.parentLabel, + longestPathLength: match.pathLength + }); + } + }); + + // Add only the parents with the most specific matches + newOpenKeys.push(...Array.from(uniqueParents.values()).map(item => item.parentLabel)); + + // Additionally, if no specific submenu match was found but the current path + // starts with one of the parent menu links, add that parent + if (newOpenKeys.length === 0) { + // Find the parent whose link is the longest prefix of the current path + let longestMatchParent = null; + let longestMatchLength = 0; + + items.forEach(item => { + if (item.links && item.links.length > 0) { + item.links.forEach(link => { + const linkPath = link.link.replace(/\/+$/, ""); + if (normalizedPathname.startsWith(linkPath + "/") && linkPath.length > longestMatchLength) { + longestMatchLength = linkPath.length; + longestMatchParent = item.label; + } + }); + } + }); + + if (longestMatchParent) { + newOpenKeys.push(longestMatchParent); + } + } + + // NEW: Check if user is on a detail page (contains ID segments or specific keywords) + const isOnDetailPage = (() => { + // Check if current path has ID-like segments or detail keywords + const segments = normalizedPathname.split('/').filter(s => s.length > 0); + + if (segments.length === 0) return false; + + const commonWords = [ + // Actions + 'detail', 'edit', 'create', 'new', 'add', 'delete', 'view', + + // Status types + 'publish', 'review', 'reject', 'status', 'active', 'inactive', 'pending', + + // General pages + 'category', 'history', 'dashboard', 'index', + + // Event specific + 'type-of-event', 'type-create', 'type-update', + + // Forum specific + 'posting', 'report-posting', 'report-comment', + + // Collaboration + 'group', + + // App Information + 'business-field', 'information-bank', 'sticker', + 'bidang-update', 'sub-bidang-update', + + // Transaction/Finance related + 'transaction-detail', 'transaction', 'payment', + 'disbursement-of-funds', 'detail-disbursement-of-funds', + 'list-disbursement-of-funds', + + // List pages (CRITICAL!) + 'list-of-investor', 'list-of-donatur', 'list-of-participants', + 'list-comment', 'list-report-comment', 'list-report-posting', + + // Input/Form pages + 'reject-input', + + // Category pages + 'category-create', 'category-update' + ]; + + const hasIdSegment = segments.some(segment => { + if (commonWords.includes(segment.toLowerCase())) { + return false; + } + + const isPureNumber = /^\d+$/.test(segment); + const isUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(segment); + const hasNumber = /\d/.test(segment); + const isAlphanumericId = /^[a-z0-9_-]+$/i.test(segment) && segment.length <= 50 && hasNumber; + + return isPureNumber || isUUID || isAlphanumericId; + }); + + return hasIdSegment; + })(); + + // NEW: Check if user is on a detail page (contains ID segments or specific keywords) + const isOnDetailPageGlobal = (() => { + // Check if current path has ID-like segments or detail keywords + const segments = normalizedPathname.split('/').filter(s => s.length > 0); + + if (segments.length === 0) return false; + + const commonWords = [ + // Actions + 'detail', 'edit', 'create', 'new', 'add', 'delete', 'view', + + // Status types + 'publish', 'review', 'reject', 'status', 'active', 'inactive', 'pending', + + // General pages + 'category', 'history', 'dashboard', 'index', + + // Event specific + 'type-of-event', 'type-create', 'type-update', + + // Forum specific + 'posting', 'report-posting', 'report-comment', + + // Collaboration + 'group', + + // App Information + 'business-field', 'information-bank', 'sticker', + 'bidang-update', 'sub-bidang-update', + + // Transaction/Finance related + 'transaction-detail', 'transaction', 'payment', + 'disbursement-of-funds', 'detail-disbursement-of-funds', + 'list-disbursement-of-funds', + + // List pages (CRITICAL!) + 'list-of-investor', 'list-of-donatur', 'list-of-participants', + 'list-comment', 'list-report-comment', 'list-report-posting', + + // Input/Form pages + 'reject-input', + + // Category pages + 'category-create', 'category-update' + ]; + + // Check if any segment is a common word + const hasCommonWord = segments.some(segment => commonWords.includes(segment.toLowerCase())); + + // Check if any segment looks like an ID (number, UUID, alphanumeric with numbers) + const hasIdSegment = segments.some(segment => { + const isPureNumber = /^\d+$/.test(segment); + const isUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(segment); + const hasNumber = /\d/.test(segment); + const isAlphanumericId = /^[a-z0-9_-]+$/i.test(segment) && segment.length <= 50 && hasNumber; + + return isPureNumber || isUUID || isAlphanumericId; + }); + + // A detail page is one that has either common words or ID segments + return hasCommonWord || hasIdSegment; + })(); + + // NEW: Only open parent menu if the current path is a detail page of the most relevant parent + if (isOnDetailPageGlobal && newOpenKeys.length === 0) { + // Find the parent whose link is the longest prefix of the current path + let longestMatchParent = null; + let longestMatchLength = 0; + + items.forEach(item => { + if (item.links && item.links.length > 0) { + item.links.forEach(link => { + const linkPath = link.link.replace(/\/+$/, ""); + if (normalizedPathname.startsWith(linkPath + "/") && linkPath.length > longestMatchLength) { + longestMatchLength = linkPath.length; + longestMatchParent = item.label; + } + }); + } + }); + + if (longestMatchParent) { + newOpenKeys.push(longestMatchParent); + } + } + + setOpenKeys(newOpenKeys); + } catch (error) { + console.error("Error in NavbarMenu useEffect:", error); + } + }, [normalizedPathname, items]); + + // Toggle dropdown + const toggleOpen = (label: string) => { + setOpenKeys((prev) => + prev.includes(label) ? prev.filter((key) => key !== label) : [...prev, label] + ); + }; + + return ( + + + {items && items.length > 0 ? ( + items.map((item) => ( + toggleOpen(item.label)} + /> + )) + ) : null} + + + ); +} + +// Komponen Item Menu +function MenuItem({ + item, + items, + onClose, + currentPath, + isOpen, + toggleOpen, +}: { + item: NavbarItem_V3; + items: NavbarItem_V3[]; + onClose?: () => void; + currentPath: string; + isOpen: boolean; + toggleOpen: () => void; +}) { + const animatedHeight = useRef(new Animated.Value(0)).current; + + // Helper function untuk check apakah path aktif + const isPathActive = (linkPath: string | undefined, detailPattern?: string) => { + if (!linkPath) return false; + const normalizedLink = linkPath.replace(/\/+$/, ""); + + // 1. Match exact - prioritas tertinggi + if (currentPath === normalizedLink) return true; + + // 2. Jika ada detailPattern, cek pattern dulu + if (detailPattern) { + // detailPattern contoh: "/admin/job/*/review" + // akan match dengan: + // - /admin/job/123/review ✅ + // - /admin/job/123/review/transaction-detail ✅ + // - /admin/job/123/review/anything/nested ✅ + const patternRegex = new RegExp( + "^" + detailPattern.replace(/\*/g, "[^/]+") + "(/.*)?$" + ); + const isMatch = patternRegex.test(currentPath); + + // Debug log untuk pattern matching + // if (currentPath.includes('transaction-detail') || currentPath.includes('disbursement')) { + // console.log('🔍 Pattern Match Check:', { + // currentPath, + // detailPattern, + // regex: patternRegex.toString(), + // isMatch + // }); + // } + + if (isMatch) { + return true; + } + } + + // 3. Match untuk detail pages (fallback) + if (currentPath.startsWith(normalizedLink + "/")) { + const remainder = currentPath.substring(normalizedLink.length + 1); + const segments = remainder.split("/").filter(s => s.length > 0); + + if (segments.length === 0) return false; + + const commonWords = [ + // Actions + 'detail', 'edit', 'create', 'new', 'add', 'delete', 'view', + + // Status types + 'publish', 'review', 'reject', 'status', 'active', 'inactive', 'pending', + + // General pages + 'category', 'history', 'index', + + // Event specific + 'type-of-event', 'type-create', 'type-update', + + // Forum specific + 'posting', 'report-posting', 'report-comment', + + // Collaboration + 'group', + + // App Information + 'business-field', 'information-bank', 'sticker', + 'bidang-update', 'sub-bidang-update', + + // Transaction/Finance related + 'transaction-detail', 'transaction', 'payment', + 'disbursement-of-funds', 'detail-disbursement-of-funds', + 'list-disbursement-of-funds', + + // List pages (CRITICAL!) + 'list-of-investor', 'list-of-donatur', 'list-of-participants', + 'list-comment', 'list-report-comment', 'list-report-posting', + + // Input/Form pages + 'reject-input', + + // Category pages + 'category-create', 'category-update' + ]; + + const hasCommonWord = segments.some(segment => + commonWords.includes(segment.toLowerCase()) + ); + + // Hanya anggap sebagai detail page jika mengandung commonWords + return hasCommonWord; + } + + return false; + }; + + // Check apakah menu item ini atau submenu-nya yang aktif + const isActive = isPathActive(item.link); + + // NEW LOGIC: Check if user is on a detail page (contains ID segments or specific keywords) + const isOnDetailPage = (() => { + // Check if current path has ID-like segments or detail keywords + const segments = currentPath.split('/').filter(s => s.length > 0); + + if (segments.length === 0) return false; + + const commonWords = [ + // Actions + 'detail', 'edit', 'create', 'new', 'add', 'delete', 'view', + + // Status types + 'publish', 'review', 'reject', 'status', 'active', 'inactive', 'pending', + + // General pages + 'category', 'history', 'dashboard', 'index', + + // Event specific + 'type-of-event', 'type-create', 'type-update', + + // Forum specific + 'posting', 'report-posting', 'report-comment', + + // Collaboration + 'group', + + // App Information + 'business-field', 'information-bank', 'sticker', + 'bidang-update', 'sub-bidang-update', + + // Transaction/Finance related + 'transaction-detail', 'transaction', 'payment', + 'disbursement-of-funds', 'detail-disbursement-of-funds', + 'list-disbursement-of-funds', + + // List pages (CRITICAL!) + 'list-of-investor', 'list-of-donatur', 'list-of-participants', + 'list-comment', 'list-report-comment', 'list-report-posting', + + // Input/Form pages + 'reject-input', + + // Category pages + 'category-create', 'category-update' + ]; + + // Check if any segment is a common word + const hasCommonWord = segments.some(segment => commonWords.includes(segment.toLowerCase())); + + // Check if any segment looks like an ID (number, UUID, alphanumeric with numbers) + const hasIdSegment = segments.some(segment => { + const isPureNumber = /^\d+$/.test(segment); + const isUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(segment); + const hasNumber = /\d/.test(segment); + const isAlphanumericId = /^[a-z0-9_-]+$/i.test(segment) && segment.length <= 50 && hasNumber; + + return isPureNumber || isUUID || isAlphanumericId; + }); + + // A detail page is one that has either common words or ID segments + return hasCommonWord || hasIdSegment; + })(); + + // Calculate all submenu active states for conflict resolution + const submenuActiveStates = item.links?.map(subItem => ({ + subItem, + isActive: isPathActive(subItem.link, subItem.detailPattern), + pathLength: subItem.link.length + })) || []; + + // Determine if any submenu is active considering conflicts + const hasActiveSubmenu = submenuActiveStates.some(({ isActive: isSubActive, pathLength, subItem }) => { + if (!isSubActive) return false; + + // Check if there's a more specific match elsewhere + const hasMoreSpecificMatch = submenuActiveStates.some(other => { + if (other.subItem.link === subItem.link) return false; // Skip self + return other.isActive && other.pathLength > pathLength; + }); + + return isSubActive && !hasMoreSpecificMatch; + }) || false; + + // For parent menu detection, if current path contains common words, + // check if this parent menu's link is a prefix of the current path + const isParentOfDetailPage = !isActive && !hasActiveSubmenu && item.links && item.links.length > 0 && + item.links.some(link => currentPath.startsWith(link.link.replace(/\/+$/, "") + "/")); + + // Determine if this is the most relevant parent menu for the current path + const isMostRelevantParent = isParentOfDetailPage && (() => { + let longestMatchLength = 0; + let mostRelevantParent = null; + + // Find the parent with the longest matching prefix + items.forEach(parentItem => { + if (parentItem.links && parentItem.links.length > 0) { + parentItem.links.forEach(link => { + const linkPath = link.link.replace(/\/+$/, ""); + if (currentPath.startsWith(linkPath + "/") && linkPath.length > longestMatchLength) { + longestMatchLength = linkPath.length; + mostRelevantParent = parentItem.label; + } + }); + } + }); + + return mostRelevantParent === item.label; + })(); + + // NEW LOGIC: If we're on a detail page, NO submenu should be active regardless of pattern matching + const hasActiveSubmenuOnDetailPage = isOnDetailPage ? false : hasActiveSubmenu; + + // NEW LOGIC: If user is on a detail page that belongs to this parent menu, + // activate only the parent menu (open dropdown) without activating any submenu + const isDetailPageOfThisMenu = !isActive && !hasActiveSubmenuOnDetailPage && + item.links && item.links.length > 0 && + item.links.some(link => { + const linkPath = link.link.replace(/\/+$/, ""); + return currentPath.startsWith(linkPath + "/"); + }) && + !isMostRelevantParent; // Only apply this logic if this isn't the most relevant parent + + // NEW LOGIC: Check if this is a page that doesn't belong to any specific menu in the navbar + const isUnlistedPage = !isActive && !hasActiveSubmenu && !isMostRelevantParent && !isDetailPageOfThisMenu && isOnDetailPage; + + // NEW LOGIC: If we're on a detail page and this menu is not the relevant parent or detail page owner, + // then it should not be highlighted even if it would normally be the most relevant + const isOnDetailPageAndNotRelevant = isOnDetailPage && !isMostRelevantParent && !isDetailPageOfThisMenu && !isActive; + + // NEW LOGIC: If this is an unlisted page, no menu should be highlighted + const isUnlistedPageAndNotRelevant = isUnlistedPage; + + // FINAL LOGIC: Only activate this menu if: + // 1. It's the exact match for current path, OR + // 2. It's the most relevant parent, OR + // 3. It's a detail page of this menu + // But NOT if we're on a detail page and this isn't the relevant parent + // And NOT if this is an unlisted page + const isActuallyRelevant = (isActive || isMostRelevantParent || isDetailPageOfThisMenu) && !isOnDetailPageAndNotRelevant && !isUnlistedPageAndNotRelevant; + + // Animasi saat isOpen berubah + useEffect(() => { + Animated.timing(animatedHeight, { + toValue: (isOpen || isDetailPageOfThisMenu) ? (item.links ? item.links.length * 44 : 0) : 0, + duration: 200, + useNativeDriver: false, + }).start(); + }, [isOpen, item.links, animatedHeight, isDetailPageOfThisMenu]); + + // Jika ada submenu + if (item.links && item.links.length > 0) { + return ( + + {/* Parent Item */} + + + + {item.label} + + + + + {/* Submenu (Animated) */} + + {submenuActiveStates.map(({ subItem, isActive: isSubActive, pathLength }, index) => { + + // CRITICAL FIX: Cek apakah ada submenu lain yang LEBIH PANJANG dan juga aktif + const hasMoreSpecificMatch = submenuActiveStates.some(other => { + if (other.subItem.link === subItem.link) return false; // Skip self + + const isOtherLonger = other.pathLength > pathLength; + + // Debug log untuk Dashboard + // if (subItem.label === "Dashboard" && isSubActive) { + // console.log(`🔎 Dashboard checking against ${other.subItem.label}:`, { + // dashboardLink: subItem.link, + // dashboardLength: pathLength, + // otherLabel: other.subItem.label, + // otherLink: other.subItem.link, + // otherPattern: other.subItem.detailPattern, + // otherLength: other.pathLength, + // otherIsActive: other.isActive, + // isOtherLonger, + // willDisableDashboard: other.isActive && isOtherLonger, + // currentURL: currentPath + // }); + // } + + // Conflict log + // if (isSubActive && other.isActive) { + // console.log('🔍 CONFLICT DETECTED:', { + // current: subItem.label, + // currentPath: subItem.link, + // currentLength: pathLength, + // other: other.subItem.label, + // otherPath: other.subItem.link, + // otherLength: other.pathLength, + // isOtherLonger, + // shouldDisableCurrent: isOtherLonger, + // currentURL: currentPath + // }); + // } + + return other.isActive && isOtherLonger; + }); + + // Final decision + const finalIsActive = isSubActive && !hasMoreSpecificMatch; + + // NEW: If this is a detail page (regardless of which menu), don't highlight any submenu items + // Also don't highlight if this is an unlisted page + const shouldHighlight = (isOnDetailPage || isUnlistedPage) ? false : finalIsActive; + + // Debug final + // if (isSubActive) { + // console.log('✅ Active check:', { + // label: subItem.label, + // link: subItem.link, + // isSubActive, + // hasMoreSpecificMatch, + // finalIsActive, + // shouldHighlight, + // isOnDetailPage, + // isUnlistedPage + // }); + // } + + return ( + { + onClose?.(); + router.push(subItem.link as any); + }} + > + + + {subItem.label} + + + ); + })} + + + ); + } + + // Menu tanpa submenu + return ( + { + onClose?.(); + router.push(item.link as any); + }} + > + + + {item.label} + + + ); +} + +// Styles +const styles = StyleSheet.create({ + container: { + marginBottom: 5, + }, + parentItem: { + flexDirection: "row", + alignItems: "center", + paddingVertical: 12, + paddingHorizontal: 10, + borderRadius: 8, + marginBottom: 5, + justifyContent: "space-between", + }, + parentItemActive: { + backgroundColor: AccentColor.blue, + }, + parentText: { + flex: 1, + fontSize: 16, + fontWeight: "500", + marginLeft: 10, + color: MainColor.white, + }, + singleItem: { + flexDirection: "row", + alignItems: "center", + paddingVertical: 12, + paddingHorizontal: 10, + borderRadius: 8, + marginBottom: 5, + }, + singleItemActive: { + backgroundColor: AccentColor.blue, + }, + singleText: { + fontSize: 16, + fontWeight: "500", + marginLeft: 10, + color: MainColor.white, + }, + icon: { + width: 24, + textAlign: "center", + paddingRight: 10, + }, + submenu: { + overflow: "hidden", + marginLeft: 30, + marginTop: 5, + }, + subItem: { + flexDirection: "row", + alignItems: "center", + paddingVertical: 8, + paddingHorizontal: 10, + borderRadius: 6, + marginBottom: 4, + }, + subItemActive: { + backgroundColor: AccentColor.blue, + }, + subText: { + color: MainColor.white, + fontSize: 16, + fontWeight: "500", + }, +}); \ No newline at end of file diff --git a/docs/prompt-for-qwen-code.md b/docs/prompt-for-qwen-code.md index d5fb8fd..3870ce2 100644 --- a/docs/prompt-for-qwen-code.md +++ b/docs/prompt-for-qwen-code.md @@ -104,18 +104,39 @@ Dalam bug diawal tadi untuk menu yang aktif jika masuk ke detail memang terseles Masih terjadi bug, mengapa saat klik menu yang memiliki dashboard maka sub menu dashboard dan sub menu yang kita klik menjadi aktif ? +Nama file: NavbarMenu_V2.tsx +Source component: components/Drawer/NavbarMenu_V2.tsx +Struktur file admin: docs/admin-folder-structure.md + +Saya mengalami bug pada file "Nama file" , saya ingin jika saat pindah halaman ( ke detail contoh : app/(application)/admin/investment/[id]/list-of-investor.tsx) maka navbar tetap menandai menu yang sedang aktif, tapi yang terjadi sekarang jika masuk ke detail maka warnanya mendeteksi dashboard padahal sedang di detail investor pada source: app/(application)/admin/investment/[id]/list-of-investor.tsx +Jika anda butuh membaca struktur file admin maka anda bisa membaca file pada "Struktur file admin" + + + + +Error terjadi pada code berikut: + items.forEach(parentItem => { + if (parentItem.links && parentItem.links.length > 0) { + parentItem.links.forEach(link => { + const linkPath = link.link.replace(/\/+$/, ""); + if (currentPath.startsWith(linkPath + "/") && linkPath.length > longestMatchLength) { + longestMatchLength = linkPath.length; + mostRelevantParent = parentItem.label; + } + }); + } + }); + + +BUG MASIH TERJADI ! Coba perbaiki perlahan , gunakan semua data dan pengetahuan meksimaln anda agar kode ini berhasil tanpa bug ! +1: Jika user masuk lebih dalam ke detail padahal bukan menu dashboard yang di pilih, CUKUP AKTIFKAN MENU YANG DI PILIH SAJA DENGAN MEMBUKA FUNGSI DROPDOWN DAN TIDAK USAH AKTIFKAN SUB MENUNYAN , INGAT ! CUKUP MENU NYA SAJA YANG AKTIF + + + Pastikan request saya terselesaikan dan error berikut clear: +- Cannot rede block-scoped variable 'hasActiveSubmenuOnDetailPage'. +- Block-scoped variable 'isOnDetailPage' used before its declaration. +- Variable 'isOnDetailPage' is used before being assigned. +- Cannot redeclare block-scoped variable 'hasActiveSubmenuOnDetailPage'. +Gunakan bahasa indonesia pada cli agar saya mudah membacanya.eclar - -export interface NavbarItem_V2 { - label: string; - icon?: keyof typeof Ionicons.glyphMap; - color?: string; - link?: string; - links?: { - label: string; - link: string; - detailPattern?: string; - }[]; - initiallyOpened?: boolean; -} \ No newline at end of file