Files
WooNooW/admin-spa/src/nav/tree.ts

146 lines
4.7 KiB
TypeScript

// Dynamic SPA menu tree (reads from backend via window.WNW_NAV_TREE)
export const NAV_TREE_VERSION = 'navTree-2025-10-28-dynamic';
export type NodeMode = 'spa' | 'bridge';
export type SubItem = {
label: string;
mode: NodeMode;
path?: string; // for SPA routes
href?: string; // for classic admin URLs
exact?: boolean;
};
export type MainKey = string; // Changed from union to string to support dynamic keys
export type MainNode = {
key: MainKey;
label: string;
path: string; // main path
icon?: string; // lucide icon name
children: SubItem[]; // will be frozen at runtime
};
/**
* Get navigation tree from backend (dynamic)
* Falls back to static tree if backend data not available
*/
function getNavTreeFromBackend(): MainNode[] {
const backendTree = (window as any).WNW_NAV_TREE;
if (Array.isArray(backendTree) && backendTree.length > 0) {
return backendTree;
}
// Fallback to static tree (for development/safety)
return getStaticFallbackTree();
}
/**
* Static fallback tree (used if backend data not available)
*/
function getStaticFallbackTree(): MainNode[] {
const admin =
(window as any).wnw?.adminUrl ??
(window as any).woonoow?.adminUrl ??
'/wp-admin/admin.php';
return [
{
key: 'dashboard',
label: 'Dashboard',
path: '/dashboard',
icon: 'layout-dashboard',
children: [
{ label: 'Overview', mode: 'spa', path: '/dashboard', exact: true },
{ label: 'Revenue', mode: 'spa', path: '/dashboard/revenue' },
{ label: 'Orders', mode: 'spa', path: '/dashboard/orders' },
{ label: 'Products', mode: 'spa', path: '/dashboard/products' },
{ label: 'Customers', mode: 'spa', path: '/dashboard/customers' },
{ label: 'Coupons', mode: 'spa', path: '/dashboard/coupons' },
{ label: 'Taxes', mode: 'spa', path: '/dashboard/taxes' },
],
},
{
key: 'orders',
label: 'Orders',
path: '/orders',
icon: 'receipt-text',
children: [],
},
{
key: 'products',
label: 'Products',
path: '/products',
icon: 'package',
children: [
{ label: 'All products', mode: 'spa', path: '/products' },
{ label: 'New', mode: 'spa', path: '/products/new' },
{ label: 'Categories', mode: 'spa', path: '/products/categories' },
{ label: 'Tags', mode: 'spa', path: '/products/tags' },
{ label: 'Attributes', mode: 'spa', path: '/products/attributes' },
],
},
{
key: 'coupons',
label: 'Coupons',
path: '/coupons',
icon: 'tag',
children: [
{ label: 'All coupons', mode: 'spa', path: '/coupons' },
{ label: 'New', mode: 'spa', path: '/coupons/new' },
],
},
{
key: 'customers',
label: 'Customers',
path: '/customers',
icon: 'users',
children: [
{ label: 'All customers', mode: 'spa', path: '/customers' },
],
},
{
key: 'settings',
label: 'Settings',
path: '/settings',
icon: 'settings',
// Settings submenu available in all modes for consistent experience
children: [
// WooNooW Settings
{ label: 'WooNooW', mode: 'spa' as const, path: '/settings' },
// WooCommerce Settings (Most Used First)
{ label: 'General', mode: 'spa' as const, path: '/settings/general' },
{ label: 'Payments', mode: 'spa' as const, path: '/settings/payments' },
{ label: 'Shipping', mode: 'spa' as const, path: '/settings/shipping' },
{ label: 'Products', mode: 'spa' as const, path: '/settings/products' },
{ label: 'Tax', mode: 'spa' as const, path: '/settings/tax' },
{ label: 'Accounts & Privacy', mode: 'spa' as const, path: '/settings/accounts' },
{ label: 'Emails', mode: 'spa' as const, path: '/settings/emails' },
// Less Common (Bridge to WP Admin for now)
{ label: 'Advanced', mode: 'bridge' as const, href: `${admin}?page=wc-settings&tab=advanced` },
{ label: 'Integration', mode: 'bridge' as const, href: `${admin}?page=wc-settings&tab=integration` },
{ label: 'Status', mode: 'bridge' as const, href: `${admin}?page=wc-status` },
{ label: 'Extensions', mode: 'bridge' as const, href: `${admin}?page=wc-addons` },
],
},
];
}
/**
* Deep freeze tree for immutability
*/
function deepFreezeTree(src: MainNode[]): MainNode[] {
return src.map((n) =>
Object.freeze({
...n,
children: Object.freeze([...(n.children ?? [])]),
}) as MainNode
) as unknown as MainNode[];
}
/**
* Export the navigation tree (reads from backend, falls back to static)
*/
export const navTree: MainNode[] = Object.freeze(
deepFreezeTree(getNavTreeFromBackend())
) as unknown as MainNode[];