import { AccentColor, MainColor } from "@/constants/color-palet"; import { Ionicons } from "@expo/vector-icons"; import { router, usePathname } from "expo-router"; import { useEffect, useRef, useState } from "react"; import { Animated, ScrollView, StyleSheet, Text, TouchableOpacity, View, } from "react-native"; export interface NavbarItem_V2 { label: string; icon?: keyof typeof Ionicons.glyphMap; color?: string; link?: string; links?: { label: string; link: string; detailPattern?: string; }[]; initiallyOpened?: boolean; } interface NavbarMenuProps { items: NavbarItem_V2[]; onClose?: () => void; } export default function NavbarMenu_V2({ 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 = [ // Event "type-create", // Other "detail", "edit", "create", "new", "add", "delete", "view", "publish", "review", "reject", "status", "category", "history", "type-of-event", "posting", "report-posting", "report-comment", "group", "dashboard", "sticker", "active", "inactive", "pending", "transaction-detail", "transaction", "payment", "disbursement", "list-of-investor", ]; 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; }; items.forEach((item) => { if (item.links && item.links.length > 0) { // Check jika ada submenu yang match dengan current path const hasActiveSubmenu = item.links.some((subItem) => { return checkPathMatch(subItem.link, subItem.detailPattern); }); if (hasActiveSubmenu) { newOpenKeys.push(item.label); } } }); 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, onClose, currentPath, isOpen, toggleOpen, }: { item: NavbarItem_V2; 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("list-of-investor") || currentPath.includes("type-create") ) { console.log( "🔍 Pattern Match Check:", JSON.stringify( { currentPath, detailPattern, regex: patternRegex.toString(), isMatch, }, null, 2, ), ); } 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 = [ // Event "type-create", "detail", "edit", "create", "new", "add", "delete", "view", "publish", "review", "reject", "status", "category", "history", "type-of-event", "posting", "report-posting", "report-comment", "group", "dashboard", "sticker", "active", "inactive", "pending", "transaction-detail", "transaction", "payment", "disbursement", ]; 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; }; // Check apakah menu item ini atau submenu-nya yang aktif const isActive = isPathActive(item.link); const hasActiveSubmenu = item.links?.some((subItem) => isPathActive(subItem.link, subItem.detailPattern), ) || false; // Animasi saat isOpen berubah useEffect(() => { Animated.timing(animatedHeight, { toValue: isOpen ? (item.links ? item.links.length * 44 : 0) : 0, duration: 200, useNativeDriver: false, }).start(); }, [isOpen, item.links, animatedHeight]); // Jika ada submenu if (item.links && item.links.length > 0) { return ( {/* Parent Item */} {item.label} {/* Submenu (Animated) */} {item.links.map((subItem, index) => { const isSubActive = isPathActive( subItem.link, subItem.detailPattern, ); // CRITICAL FIX: Jika submenu ini aktif, cek apakah ada submenu lain yang LEBIH PANJANG dan juga aktif // Jika ada yang lebih panjang dan aktif, maka yang pendek TIDAK AKTIF const hasMoreSpecificMatch = item.links!.some((otherSubItem) => { if (otherSubItem.link === subItem.link) return false; // Skip self const otherIsActive = isPathActive( otherSubItem.link, otherSubItem.detailPattern, ); const isOtherLonger = otherSubItem.link.length > subItem.link.length; // Debug log if (isSubActive && otherIsActive) { console.log( "🔍 CONFLICT DETECTED:", JSON.stringify( { current: subItem.label, currentPath: subItem.link, currentLength: subItem.link.length, other: otherSubItem.label, otherPath: otherSubItem.link, otherLength: otherSubItem.link.length, isOtherLonger, currentURL: currentPath, }, null, 2, ), ); } // Jika submenu lain JUGA aktif DAN lebih panjang (lebih spesifik), // maka submenu yang pendek ini TIDAK boleh aktif return otherIsActive && isOtherLonger; }); // Final decision: aktif HANYA jika match DAN tidak ada yang lebih spesifik const finalIsActive = isSubActive && !hasMoreSpecificMatch; // Debug final decision if (isSubActive) { console.log( "✅ Active check:", JSON.stringify( { label: subItem.label, link: subItem.link, isSubActive, hasMoreSpecificMatch, finalIsActive, }, null, 2, ), ); } 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", }, });