Fixed navbar admin
User Layout - app/(application)/(user)/home.tsx Components - components/Drawer/NavbarMenu_V2.tsx Docs - docs/admin-folder-structure.md ### Issue: saat masuk lebih dalam ke sub menu indikator aktif di navbar hilang
This commit is contained in:
@@ -77,14 +77,14 @@ export default function Application() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (data && data?.masterUserRoleId !== "1") {
|
if (data && data?.masterUserRoleId !== "1") {
|
||||||
// console.log("User is not admin");
|
console.log("User is not admin");
|
||||||
// return (
|
return (
|
||||||
// <BasicWrapper>
|
<BasicWrapper>
|
||||||
// <Redirect href={`/admin/dashboard`} />
|
<Redirect href={`/admin/dashboard`} />
|
||||||
// </BasicWrapper>
|
</BasicWrapper>
|
||||||
// );
|
);
|
||||||
// }
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import { router, usePathname } from "expo-router";
|
import { router, usePathname } from "expo-router";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Animated,
|
Animated,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
@@ -19,7 +19,7 @@ export interface NavbarItem_V2 {
|
|||||||
links?: {
|
links?: {
|
||||||
label: string;
|
label: string;
|
||||||
link: string;
|
link: string;
|
||||||
detailPattern?: string;
|
detailPattern?: string; // NEW: Pattern untuk match detail pages
|
||||||
}[];
|
}[];
|
||||||
initiallyOpened?: boolean;
|
initiallyOpened?: boolean;
|
||||||
}
|
}
|
||||||
@@ -45,93 +45,89 @@ export default function NavbarMenu_V2({ items, onClose }: NavbarMenuProps) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const newOpenKeys: string[] = [];
|
const newOpenKeys: string[] = [];
|
||||||
|
|
||||||
// Helper function yang sama dengan di MenuItem
|
// Helper function yang sama dengan di MenuItem
|
||||||
const checkPathMatch = (linkPath: string, detailPattern?: string) => {
|
const checkPathMatch = (linkPath: string, detailPattern?: string) => {
|
||||||
const normalizedLink = linkPath.replace(/\/+$/, "");
|
const normalizedLink = linkPath.replace(/\/+$/, "");
|
||||||
|
|
||||||
// Exact match
|
// Exact match
|
||||||
if (normalizedPathname === normalizedLink) return true;
|
if (normalizedPathname === normalizedLink) return true;
|
||||||
|
|
||||||
// Detail pattern match
|
// Detail pattern match
|
||||||
if (detailPattern) {
|
if (detailPattern) {
|
||||||
const patternRegex = new RegExp(
|
const patternRegex = new RegExp(
|
||||||
"^" + detailPattern.replace(/\*/g, "[^/]+") + "(/.*)?$",
|
"^" + detailPattern.replace(/\*/g, "[^/]+") + "(/.*)?$"
|
||||||
);
|
);
|
||||||
if (patternRegex.test(normalizedPathname)) {
|
if (patternRegex.test(normalizedPathname)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detail page match (fallback)
|
// Detail page match (fallback)
|
||||||
if (normalizedPathname.startsWith(normalizedLink + "/")) {
|
if (normalizedPathname.startsWith(normalizedLink + "/")) {
|
||||||
const remainder = normalizedPathname.substring(
|
const remainder = normalizedPathname.substring(normalizedLink.length + 1);
|
||||||
normalizedLink.length + 1,
|
const segments = remainder.split("/").filter(s => s.length > 0);
|
||||||
);
|
|
||||||
const segments = remainder.split("/").filter((s) => s.length > 0);
|
|
||||||
|
|
||||||
if (segments.length === 0) return false;
|
if (segments.length === 0) return false;
|
||||||
|
|
||||||
const commonWords = [
|
const commonWords = [
|
||||||
// Event
|
// Actions
|
||||||
"type-create",
|
'detail', 'edit', 'create', 'new', 'add', 'delete', 'view',
|
||||||
|
|
||||||
// Other
|
// Status types
|
||||||
"detail",
|
'publish', 'review', 'reject', 'status', 'active', 'inactive', 'pending',
|
||||||
"edit",
|
|
||||||
"create",
|
// General pages
|
||||||
"new",
|
'category', 'history', 'dashboard', 'index',
|
||||||
"add",
|
|
||||||
"delete",
|
// Event specific
|
||||||
"view",
|
'type-of-event', 'type-create', 'type-update',
|
||||||
"publish",
|
|
||||||
"review",
|
// Forum specific
|
||||||
"reject",
|
'posting', 'report-posting', 'report-comment',
|
||||||
"status",
|
|
||||||
"category",
|
// Collaboration
|
||||||
"history",
|
'group',
|
||||||
"type-of-event",
|
|
||||||
"posting",
|
// App Information
|
||||||
"report-posting",
|
'business-field', 'information-bank', 'sticker',
|
||||||
"report-comment",
|
'bidang-update', 'sub-bidang-update',
|
||||||
"group",
|
|
||||||
"dashboard",
|
// Transaction/Finance related
|
||||||
"sticker",
|
'transaction-detail', 'transaction', 'payment',
|
||||||
"active",
|
'disbursement-of-funds', 'detail-disbursement-of-funds',
|
||||||
"inactive",
|
'list-disbursement-of-funds',
|
||||||
"pending",
|
|
||||||
"transaction-detail",
|
// List pages (CRITICAL!)
|
||||||
"transaction",
|
'list-of-investor', 'list-of-donatur', 'list-of-participants',
|
||||||
"payment",
|
'list-comment', 'list-report-comment', 'list-report-posting',
|
||||||
"disbursement",
|
|
||||||
"list-of-investor",
|
// Input/Form pages
|
||||||
|
'reject-input',
|
||||||
|
|
||||||
|
// Category pages
|
||||||
|
'category-create', 'category-update'
|
||||||
];
|
];
|
||||||
|
|
||||||
const hasIdSegment = segments.some((segment) => {
|
const hasIdSegment = segments.some(segment => {
|
||||||
if (commonWords.includes(segment.toLowerCase())) {
|
if (commonWords.includes(segment.toLowerCase())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isPureNumber = /^\d+$/.test(segment);
|
const isPureNumber = /^\d+$/.test(segment);
|
||||||
const isUUID =
|
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);
|
||||||
/^[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 hasNumber = /\d/.test(segment);
|
||||||
const isAlphanumericId =
|
const isAlphanumericId = /^[a-z0-9_-]+$/i.test(segment) && segment.length <= 50 && hasNumber;
|
||||||
/^[a-z0-9_-]+$/i.test(segment) &&
|
|
||||||
segment.length <= 50 &&
|
|
||||||
hasNumber;
|
|
||||||
|
|
||||||
return isPureNumber || isUUID || isAlphanumericId;
|
return isPureNumber || isUUID || isAlphanumericId;
|
||||||
});
|
});
|
||||||
|
|
||||||
return hasIdSegment;
|
return hasIdSegment;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
items.forEach((item) => {
|
items.forEach((item) => {
|
||||||
if (item.links && item.links.length > 0) {
|
if (item.links && item.links.length > 0) {
|
||||||
// Check jika ada submenu yang match dengan current path
|
// Check jika ada submenu yang match dengan current path
|
||||||
@@ -154,9 +150,7 @@ export default function NavbarMenu_V2({ items, onClose }: NavbarMenuProps) {
|
|||||||
// Toggle dropdown
|
// Toggle dropdown
|
||||||
const toggleOpen = (label: string) => {
|
const toggleOpen = (label: string) => {
|
||||||
setOpenKeys((prev) =>
|
setOpenKeys((prev) =>
|
||||||
prev.includes(label)
|
prev.includes(label) ? prev.filter((key) => key !== label) : [...prev, label]
|
||||||
? prev.filter((key) => key !== label)
|
|
||||||
: [...prev, label],
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -171,18 +165,18 @@ export default function NavbarMenu_V2({ items, onClose }: NavbarMenuProps) {
|
|||||||
paddingVertical: 10,
|
paddingVertical: 10,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{items && items.length > 0
|
{items && items.length > 0 ? (
|
||||||
? items.map((item) => (
|
items.map((item) => (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key={item.label}
|
key={item.label}
|
||||||
item={item}
|
item={item}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
currentPath={normalizedPathname}
|
currentPath={normalizedPathname}
|
||||||
isOpen={openKeys.includes(item.label)}
|
isOpen={openKeys.includes(item.label)}
|
||||||
toggleOpen={() => toggleOpen(item.label)}
|
toggleOpen={() => toggleOpen(item.label)}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
: null}
|
) : null}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@@ -205,121 +199,109 @@ function MenuItem({
|
|||||||
const animatedHeight = useRef(new Animated.Value(0)).current;
|
const animatedHeight = useRef(new Animated.Value(0)).current;
|
||||||
|
|
||||||
// Helper function untuk check apakah path aktif
|
// Helper function untuk check apakah path aktif
|
||||||
const isPathActive = (
|
const isPathActive = (linkPath: string | undefined, detailPattern?: string) => {
|
||||||
linkPath: string | undefined,
|
|
||||||
detailPattern?: string,
|
|
||||||
) => {
|
|
||||||
if (!linkPath) return false;
|
if (!linkPath) return false;
|
||||||
const normalizedLink = linkPath.replace(/\/+$/, "");
|
const normalizedLink = linkPath.replace(/\/+$/, "");
|
||||||
|
|
||||||
// 1. Match exact - prioritas tertinggi
|
// 1. Match exact - prioritas tertinggi
|
||||||
if (currentPath === normalizedLink) return true;
|
if (currentPath === normalizedLink) return true;
|
||||||
|
|
||||||
// 2. Jika ada detailPattern, cek pattern dulu
|
// 2. Jika ada detailPattern, cek pattern dulu
|
||||||
if (detailPattern) {
|
if (detailPattern) {
|
||||||
// detailPattern contoh: "/admin/job/*/review"
|
// detailPattern contoh: "/admin/job/*/review"
|
||||||
// akan match dengan:
|
// akan match dengan:
|
||||||
// - /admin/job/123/review ✅
|
// - /admin/job/123/review ✅
|
||||||
// - /admin/job/123/review/transaction-detail ✅
|
// - /admin/job/123/review/transaction-detail ✅
|
||||||
// - /admin/job/123/review/anything/nested ✅
|
// - /admin/job/123/review/anything/nested ✅
|
||||||
const patternRegex = new RegExp(
|
const patternRegex = new RegExp(
|
||||||
"^" + detailPattern.replace(/\*/g, "[^/]+") + "(/.*)?$",
|
"^" + detailPattern.replace(/\*/g, "[^/]+") + "(/.*)?$"
|
||||||
);
|
);
|
||||||
const isMatch = patternRegex.test(currentPath);
|
const isMatch = patternRegex.test(currentPath);
|
||||||
|
|
||||||
// Debug log untuk pattern matching
|
// Debug log untuk pattern matching
|
||||||
if (
|
if (currentPath.includes('transaction-detail') || currentPath.includes('disbursement')) {
|
||||||
currentPath.includes("list-of-investor") ||
|
console.log('🔍 Pattern Match Check:', {
|
||||||
currentPath.includes("type-create")
|
currentPath,
|
||||||
) {
|
detailPattern,
|
||||||
console.log(
|
regex: patternRegex.toString(),
|
||||||
"🔍 Pattern Match Check:",
|
isMatch
|
||||||
JSON.stringify(
|
});
|
||||||
{
|
|
||||||
currentPath,
|
|
||||||
detailPattern,
|
|
||||||
regex: patternRegex.toString(),
|
|
||||||
isMatch,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMatch) {
|
if (isMatch) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Match untuk detail pages (fallback)
|
// 3. Match untuk detail pages (fallback)
|
||||||
if (currentPath.startsWith(normalizedLink + "/")) {
|
if (currentPath.startsWith(normalizedLink + "/")) {
|
||||||
const remainder = currentPath.substring(normalizedLink.length + 1);
|
const remainder = currentPath.substring(normalizedLink.length + 1);
|
||||||
const segments = remainder.split("/").filter((s) => s.length > 0);
|
const segments = remainder.split("/").filter(s => s.length > 0);
|
||||||
|
|
||||||
if (segments.length === 0) return false;
|
if (segments.length === 0) return false;
|
||||||
|
|
||||||
const commonWords = [
|
const commonWords = [
|
||||||
// Event
|
// Actions
|
||||||
"type-create",
|
'detail', 'edit', 'create', 'new', 'add', 'delete', 'view',
|
||||||
"detail",
|
|
||||||
"edit",
|
// Status types
|
||||||
"create",
|
'publish', 'review', 'reject', 'status', 'active', 'inactive', 'pending',
|
||||||
"new",
|
|
||||||
"add",
|
// General pages
|
||||||
"delete",
|
'category', 'history', 'dashboard', 'index',
|
||||||
"view",
|
|
||||||
"publish",
|
// Event specific
|
||||||
"review",
|
'type-of-event', 'type-create', 'type-update',
|
||||||
"reject",
|
|
||||||
"status",
|
// Forum specific
|
||||||
"category",
|
'posting', 'report-posting', 'report-comment',
|
||||||
"history",
|
|
||||||
"type-of-event",
|
// Collaboration
|
||||||
"posting",
|
'group',
|
||||||
"report-posting",
|
|
||||||
"report-comment",
|
// App Information
|
||||||
"group",
|
'business-field', 'information-bank', 'sticker',
|
||||||
"dashboard",
|
'bidang-update', 'sub-bidang-update',
|
||||||
"sticker",
|
|
||||||
"active",
|
// Transaction/Finance related
|
||||||
"inactive",
|
'transaction-detail', 'transaction', 'payment',
|
||||||
"pending",
|
'disbursement-of-funds', 'detail-disbursement-of-funds',
|
||||||
"transaction-detail",
|
'list-disbursement-of-funds',
|
||||||
"transaction",
|
|
||||||
"payment",
|
// List pages (CRITICAL!)
|
||||||
"disbursement",
|
'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) => {
|
const hasIdSegment = segments.some(segment => {
|
||||||
if (commonWords.includes(segment.toLowerCase())) {
|
if (commonWords.includes(segment.toLowerCase())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isPureNumber = /^\d+$/.test(segment);
|
const isPureNumber = /^\d+$/.test(segment);
|
||||||
const isUUID =
|
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);
|
||||||
/^[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 hasNumber = /\d/.test(segment);
|
||||||
const isAlphanumericId =
|
const isAlphanumericId = /^[a-z0-9_-]+$/i.test(segment) && segment.length <= 50 && hasNumber;
|
||||||
/^[a-z0-9_-]+$/i.test(segment) && segment.length <= 50 && hasNumber;
|
|
||||||
|
|
||||||
return isPureNumber || isUUID || isAlphanumericId;
|
return isPureNumber || isUUID || isAlphanumericId;
|
||||||
});
|
});
|
||||||
|
|
||||||
return hasIdSegment;
|
return hasIdSegment;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check apakah menu item ini atau submenu-nya yang aktif
|
// Check apakah menu item ini atau submenu-nya yang aktif
|
||||||
const isActive = isPathActive(item.link);
|
const isActive = isPathActive(item.link);
|
||||||
const hasActiveSubmenu =
|
const hasActiveSubmenu =
|
||||||
item.links?.some((subItem) =>
|
item.links?.some((subItem) => isPathActive(subItem.link, subItem.detailPattern)) || false;
|
||||||
isPathActive(subItem.link, subItem.detailPattern),
|
|
||||||
) || false;
|
|
||||||
|
|
||||||
// Animasi saat isOpen berubah
|
// Animasi saat isOpen berubah
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -332,6 +314,13 @@ function MenuItem({
|
|||||||
|
|
||||||
// Jika ada submenu
|
// Jika ada submenu
|
||||||
if (item.links && item.links.length > 0) {
|
if (item.links && item.links.length > 0) {
|
||||||
|
// PRE-CALCULATE semua active states untuk submenu
|
||||||
|
const submenuActiveStates = item.links.map(subItem => ({
|
||||||
|
subItem,
|
||||||
|
isActive: isPathActive(subItem.link, subItem.detailPattern),
|
||||||
|
pathLength: subItem.link.length
|
||||||
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
{/* Parent Item */}
|
{/* Parent Item */}
|
||||||
@@ -377,71 +366,62 @@ function MenuItem({
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{item.links.map((subItem, index) => {
|
{submenuActiveStates.map(({ subItem, isActive: isSubActive, pathLength }, index) => {
|
||||||
const isSubActive = isPathActive(
|
|
||||||
subItem.link,
|
// CRITICAL FIX: Cek apakah ada submenu lain yang LEBIH PANJANG dan juga aktif
|
||||||
subItem.detailPattern,
|
const hasMoreSpecificMatch = submenuActiveStates.some(other => {
|
||||||
);
|
if (other.subItem.link === subItem.link) return false; // Skip self
|
||||||
|
|
||||||
// CRITICAL FIX: Jika submenu ini aktif, cek apakah ada submenu lain yang LEBIH PANJANG dan juga aktif
|
const isOtherLonger = other.pathLength > pathLength;
|
||||||
// Jika ada yang lebih panjang dan aktif, maka yang pendek TIDAK AKTIF
|
|
||||||
const hasMoreSpecificMatch = item.links!.some((otherSubItem) => {
|
// Debug log untuk Dashboard
|
||||||
if (otherSubItem.link === subItem.link) return false; // Skip self
|
if (subItem.label === "Dashboard" && isSubActive) {
|
||||||
|
console.log(`🔎 Dashboard checking against ${other.subItem.label}:`, {
|
||||||
const otherIsActive = isPathActive(
|
dashboardLink: subItem.link,
|
||||||
otherSubItem.link,
|
dashboardLength: pathLength,
|
||||||
otherSubItem.detailPattern,
|
otherLabel: other.subItem.label,
|
||||||
);
|
otherLink: other.subItem.link,
|
||||||
const isOtherLonger =
|
otherPattern: other.subItem.detailPattern,
|
||||||
otherSubItem.link.length > subItem.link.length;
|
otherLength: other.pathLength,
|
||||||
|
otherIsActive: other.isActive,
|
||||||
// Debug log
|
isOtherLonger,
|
||||||
if (isSubActive && otherIsActive) {
|
willDisableDashboard: other.isActive && isOtherLonger,
|
||||||
console.log(
|
currentURL: currentPath
|
||||||
"🔍 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),
|
// Conflict log
|
||||||
// maka submenu yang pendek ini TIDAK boleh aktif
|
if (isSubActive && other.isActive) {
|
||||||
return otherIsActive && isOtherLonger;
|
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: aktif HANYA jika match DAN tidak ada yang lebih spesifik
|
// Final decision
|
||||||
const finalIsActive = isSubActive && !hasMoreSpecificMatch;
|
const finalIsActive = isSubActive && !hasMoreSpecificMatch;
|
||||||
|
|
||||||
// Debug final decision
|
// Debug final
|
||||||
if (isSubActive) {
|
if (isSubActive) {
|
||||||
console.log(
|
console.log('✅ Active check:', {
|
||||||
"✅ Active check:",
|
label: subItem.label,
|
||||||
JSON.stringify(
|
link: subItem.link,
|
||||||
{
|
isSubActive,
|
||||||
label: subItem.label,
|
hasMoreSpecificMatch,
|
||||||
link: subItem.link,
|
finalIsActive
|
||||||
isSubActive,
|
});
|
||||||
hasMoreSpecificMatch,
|
|
||||||
finalIsActive,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
key={index}
|
key={index}
|
||||||
@@ -567,4 +547,4 @@ const styles = StyleSheet.create({
|
|||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: "500",
|
fontWeight: "500",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
177
docs/admin-folder-structure.md
Normal file
177
docs/admin-folder-structure.md
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
# Struktur Folder Admin Aplikasi HIPMI Mobile
|
||||||
|
|
||||||
|
Dokumen ini menjelaskan struktur folder dan file untuk bagian admin dari aplikasi HIPMI Mobile yang terletak di `app/(application)/admin`.
|
||||||
|
|
||||||
|
## File dan Folder Tingkat Atas
|
||||||
|
|
||||||
|
### Folder
|
||||||
|
- `app-information` - Manajemen informasi aplikasi
|
||||||
|
- `collaboration` - Manajemen modul kolaborasi
|
||||||
|
- `donation` - Manajemen modul donasi
|
||||||
|
- `event` - Manajemen modul acara
|
||||||
|
- `forum` - Manajemen modul forum
|
||||||
|
- `investment` - Manajemen modul investasi
|
||||||
|
- `job` - Manajemen modul lowongan kerja
|
||||||
|
- `notification` - Manajemen notifikasi
|
||||||
|
- `super-admin` - Fungsi super admin
|
||||||
|
- `user-access` - Manajemen akses pengguna
|
||||||
|
- `voting` - Manajemen modul voting
|
||||||
|
|
||||||
|
### File
|
||||||
|
- `_layout.tsx` - Komponen tata letak untuk bagian admin
|
||||||
|
- `dashboard.tsx` - Tampilan dasbor admin
|
||||||
|
- `maps.tsx` - Fungsionalitas peta untuk admin
|
||||||
|
|
||||||
|
## Struktur Folder Terperinci
|
||||||
|
|
||||||
|
### app-information/
|
||||||
|
```
|
||||||
|
app-information/
|
||||||
|
├── business-field/
|
||||||
|
│ ├── [id]/
|
||||||
|
│ │ ├── bidang-update.tsx
|
||||||
|
│ │ ├── index.tsx
|
||||||
|
│ │ └── sub-bidang-update.tsx
|
||||||
|
│ └── create.tsx
|
||||||
|
├── information-bank/
|
||||||
|
│ ├── [id]/
|
||||||
|
│ │ └── index.tsx
|
||||||
|
│ └── create.tsx
|
||||||
|
├── sticker/
|
||||||
|
│ ├── [id]/
|
||||||
|
│ │ └── index.tsx
|
||||||
|
│ └── create.tsx
|
||||||
|
└── index.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
### collaboration/
|
||||||
|
```
|
||||||
|
collaboration/
|
||||||
|
├── [id]/
|
||||||
|
│ ├── [status].tsx
|
||||||
|
│ ├── group.tsx
|
||||||
|
│ └── reject-input.tsx
|
||||||
|
├── group.tsx
|
||||||
|
├── index.tsx
|
||||||
|
├── publish.tsx
|
||||||
|
└── reject.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
### donation/
|
||||||
|
```
|
||||||
|
donation/
|
||||||
|
├── [id]/
|
||||||
|
│ ├── [status]/
|
||||||
|
│ │ ├── index.tsx
|
||||||
|
│ │ └── transaction-detail.tsx
|
||||||
|
│ ├── detail-disbursement-of-funds.tsx
|
||||||
|
│ ├── disbursement-of-funds.tsx
|
||||||
|
│ ├── list-disbursement-of-funds.tsx
|
||||||
|
│ ├── list-of-donatur.tsx
|
||||||
|
│ └── reject-input.tsx
|
||||||
|
├── [status]/
|
||||||
|
│ └── status.tsx
|
||||||
|
├── category-create.tsx
|
||||||
|
├── category-update.tsx
|
||||||
|
├── category.tsx
|
||||||
|
└── index.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
### event/
|
||||||
|
```
|
||||||
|
event/
|
||||||
|
├── [id]/
|
||||||
|
│ ├── [status]/
|
||||||
|
│ │ └── index.tsx
|
||||||
|
│ ├── list-of-participants.tsx
|
||||||
|
│ └── reject-input.tsx
|
||||||
|
├── [status]/
|
||||||
|
│ └── status.tsx
|
||||||
|
├── index.tsx
|
||||||
|
├── type-create.tsx
|
||||||
|
├── type-of-event.tsx
|
||||||
|
└── type-update.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
### forum/
|
||||||
|
```
|
||||||
|
forum/
|
||||||
|
├── [id]/
|
||||||
|
│ ├── index.tsx
|
||||||
|
│ ├── list-comment.tsx
|
||||||
|
│ ├── list-report-comment.tsx
|
||||||
|
│ └── list-report-posting.tsx
|
||||||
|
├── index.tsx
|
||||||
|
├── posting.tsx
|
||||||
|
├── report-comment.tsx
|
||||||
|
└── report-posting.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
### investment/
|
||||||
|
```
|
||||||
|
investment/
|
||||||
|
├── [id]/
|
||||||
|
│ ├── [status]/
|
||||||
|
│ │ ├── index.tsx
|
||||||
|
│ │ └── transaction-detail.tsx
|
||||||
|
│ ├── list-of-investor.tsx
|
||||||
|
│ └── reject-input.tsx
|
||||||
|
├── [status]/
|
||||||
|
│ └── status.tsx
|
||||||
|
└── index.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
### job/
|
||||||
|
```
|
||||||
|
job/
|
||||||
|
├── [id]/
|
||||||
|
│ ├── [status]/
|
||||||
|
│ │ ├── index.tsx
|
||||||
|
│ │ └── reject-input.tsx
|
||||||
|
├── [status]/
|
||||||
|
│ └── status.tsx
|
||||||
|
└── index.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
### notification/
|
||||||
|
```
|
||||||
|
notification/
|
||||||
|
└── index.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
### super-admin/
|
||||||
|
```
|
||||||
|
super-admin/
|
||||||
|
├── [id]/
|
||||||
|
│ └── index.tsx
|
||||||
|
└── index.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
### user-access/
|
||||||
|
```
|
||||||
|
user-access/
|
||||||
|
├── [id]/
|
||||||
|
│ └── index.tsx
|
||||||
|
└── index.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
### voting/
|
||||||
|
```
|
||||||
|
voting/
|
||||||
|
├── [id]/
|
||||||
|
│ ├── [status]/
|
||||||
|
│ │ ├── index.tsx
|
||||||
|
│ │ └── reject-input.tsx
|
||||||
|
├── [status]/
|
||||||
|
│ └── status.tsx
|
||||||
|
├── history.tsx
|
||||||
|
└── index.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rute Dinamis
|
||||||
|
|
||||||
|
Bagian admin menggunakan rute dinamis yang ditunjukkan dengan kurung siku `[ ]`:
|
||||||
|
- `[id]` - Rute dinamis untuk ID item tertentu
|
||||||
|
- `[status]` - Rute dinamis untuk tampilan berdasarkan status
|
||||||
|
|
||||||
|
Ini memungkinkan routing yang fleksibel berdasarkan parameter tertentu seperti ID item atau status.
|
||||||
Reference in New Issue
Block a user