Fixed navbar admin

User & Admin Layout
- app/(application)/(user)/home.tsx
- app/(application)/admin/_layout.tsx

Components
- components/Drawer/NavbarMenu.tsx
- components/index.ts

Docs
- docs/prompt-for-qwen-code.md

Backup Component
- components/Drawer/NavbarMenu.back.tsx

New Components
- components/Drawer/NavbarMenu_V2.tsx
- components/_ShareComponent/BasicWrapper.tsx

New Admin Screen
- screens/Admin/listPageAdmin_V2.tsx

### No Issue
This commit is contained in:
2026-02-11 17:40:08 +08:00
parent b2be7be533
commit 5c931b069c
9 changed files with 1512 additions and 11 deletions

View File

@@ -37,6 +37,90 @@ export default function NavbarMenu({ items, onClose }: NavbarMenuProps) {
const normalizePath = (path: string) => path.replace(/\/+$/, "");
const normalizedPathname = pathname ? normalizePath(pathname) : "";
// Fungsi untuk mengecek apakah path cocok dengan item menu
// Ini akan mengecek kecocokan eksak atau pola path
const isActivePath = (itemPath: string | undefined): boolean => {
if (!itemPath || !normalizedPathname) return false;
// Cocokan eksak
if (normalizePath(itemPath) === normalizedPathname) return true;
// Cocokan pola path seperti /user-access/[id]/index dengan /user-access/index
// atau /donation/[id]/detail dengan /donation/index
const normalizedItemPath = normalizePath(itemPath);
// Jika path item adalah bagian dari path saat ini (prefix match)
if (normalizedPathname.startsWith(normalizedItemPath + '/')) return true;
// Jika path saat ini adalah bagian dari path item (misalnya /user-access/detail cocok dengan /user-access)
if (normalizedItemPath.startsWith(normalizedPathname + '/')) return true;
// Jika path item adalah bagian dari path saat ini tanpa id (misalnya /user-access/[id]/index cocok dengan /user-access/index)
const itemParts = normalizedItemPath.split('/');
const currentParts = normalizedPathname.split('/');
// Jika panjangnya sama dan hanya berbeda di bagian dinamis [id]
if (itemParts.length === currentParts.length) {
let match = true;
for (let i = 0; i < itemParts.length; i++) {
// Jika bagian path item adalah placeholder [id], abaikan
if (itemParts[i].startsWith('[') && itemParts[i].endsWith(']')) continue;
// Jika bagian path saat ini adalah ID (angka), abaikan
if (/^\d+$/.test(currentParts[i])) continue;
// Jika tidak cocok dan bukan placeholder atau ID, maka tidak cocok
if (itemParts[i] !== currentParts[i]) {
match = false;
break;
}
}
if (match) return true;
}
// Tambahkan logika khusus untuk menangani file index.tsx sebagai halaman dashboard
// Jika path saat ini adalah versi index dari path item (misalnya /admin/event/index cocok dengan /admin/event)
if (normalizedPathname === normalizedItemPath + '/index') return true;
return false;
};
// Fungsi untuk menentukan item mana yang paling spesifik aktif
// Ini akan memastikan hanya satu item yang aktif pada satu waktu
const findMostSpecificActiveItem = (): { parentLabel?: string; subItemLink?: string } | null => {
// Cek setiap item menu
for (const item of items) {
// Jika item memiliki sub-menu
if (item.links && item.links.length > 0) {
// Urutkan sub-menu berdasarkan panjang path (terpanjang dulu untuk prioritas lebih spesifik)
const sortedSubItems = [...item.links].sort((a, b) => {
if (a.link && b.link) {
return b.link.length - a.link.length; // Urutan menurun (terpanjang dulu)
}
return 0;
});
// Cek setiap sub-menu dalam urutan yang telah diurutkan
for (const subItem of sortedSubItems) {
if (isActivePath(subItem.link)) {
return { parentLabel: item.label, subItemLink: subItem.link };
}
}
}
// Jika tidak ada sub-menu yang cocok, cek item utama
if (isActivePath(item.link)) {
return { parentLabel: item.label };
}
}
return null;
};
// Hitung item aktif terlebih dahulu
const mostSpecificActive = findMostSpecificActiveItem();
// Set activeLink saat pathname berubah
useEffect(() => {
if (normalizedPathname) {
@@ -44,6 +128,15 @@ export default function NavbarMenu({ items, onClose }: NavbarMenuProps) {
}
}, [normalizedPathname]);
// Fungsi untuk menentukan apakah dropdown harus tetap terbuka
// Dropdown tetap terbuka jika salah satu dari sub-menu cocok dengan path saat ini
const shouldDropdownBeOpen = (item: NavbarItem): boolean => {
if (!normalizedPathname || !item.links || item.links.length === 0) return false;
// Cek apakah salah satu sub-menu cocok dengan path saat ini
return item.links.some(subItem => isActivePath(subItem.link));
};
// Toggle dropdown
const toggleOpen = (label: string) => {
setOpenKeys((prev) =>
@@ -56,7 +149,7 @@ export default function NavbarMenu({ items, onClose }: NavbarMenuProps) {
style={{
// flex: 1,
// backgroundColor: MainColor.black,
marginBottom: 20,
marginBottom: 20,
}}
>
<ScrollView
@@ -72,8 +165,21 @@ export default function NavbarMenu({ items, onClose }: NavbarMenuProps) {
onClose={onClose}
activeLink={activeLink}
setActiveLink={setActiveLink}
isOpen={openKeys.includes(item.label)}
isOpen={openKeys.includes(item.label) || shouldDropdownBeOpen(item)}
toggleOpen={() => toggleOpen(item.label)}
isActivePath={isActivePath}
isMostSpecificActive={(menuItem) => {
if (!mostSpecificActive) return false;
// Jika item memiliki sub-menu
if (menuItem.links && menuItem.links.length > 0) {
// Jika item ini adalah parent dari sub-menu yang aktif, menu utama tidak aktif
return false;
}
// Jika tidak ada sub-menu, hanya periksa kecocokan langsung
return mostSpecificActive.parentLabel === menuItem.label && !mostSpecificActive.subItemLink;
}}
/>
))}
</ScrollView>
@@ -89,6 +195,8 @@ function MenuItem({
setActiveLink,
isOpen,
toggleOpen,
isActivePath,
isMostSpecificActive,
}: {
item: NavbarItem;
onClose?: () => void;
@@ -96,8 +204,10 @@ function MenuItem({
setActiveLink: (link: string | null) => void;
isOpen: boolean;
toggleOpen: () => void;
isActivePath: (itemPath: string | undefined) => boolean;
isMostSpecificActive: (item: NavbarItem) => boolean;
}) {
const isActive = activeLink === item.link;
const isActive = isMostSpecificActive(item);
const animatedHeight = useRef(new Animated.Value(0)).current;
// Animasi saat isOpen berubah
@@ -121,7 +231,9 @@ function MenuItem({
color={MainColor.white}
style={styles.icon}
/>
<Text style={styles.parentText}>{item.label}</Text>
<Text style={styles.parentText}>
{item.label}
</Text>
<Ionicons
name={isOpen ? "chevron-up" : "chevron-down"}
size={16}
@@ -147,7 +259,8 @@ function MenuItem({
]}
>
{item.links.map((subItem, index) => {
const isSubActive = activeLink === subItem.link;
// Untuk sub-item, kita gunakan logika aktif berdasarkan isActivePath
const isSubActive = isActivePath(subItem.link);
return (
<TouchableOpacity
key={index}