feat: implement coming soon pages and enhance menu structure
- Added a new ComingSoonPage component for displaying a status page for features that are not yet available. - Integrated the ComingSoonPage into the sales and logistics modules for routes such as invoices, payments, packing slips, and reports. - Updated the MENU_ITEMS structure to include new nested items for sales and logistics, enhancing the organization of the menu. - Introduced placeholder functionality in the menu to manage visibility of items that are not yet available. - Updated navigation labels in both English and Indonesian to reflect new menu items and improve user experience. These changes enhance the application's navigation and user experience by clearly indicating upcoming features and improving menu organization.
This commit is contained in:
@@ -4,6 +4,7 @@ import { ThemeProvider } from '@repo/ui/provider';
|
||||
import { StatusPage, AgGridProvider } from '@repo/ui/components';
|
||||
import { useTranslation } from '@repo/core-i18n';
|
||||
import { LoadingScreen } from '../core/components/loading-screen';
|
||||
import { ComingSoonPage } from '../core/components/coming-soon-page';
|
||||
import { useThemeStore } from '../core/stores/theme.store';
|
||||
import { initializeAndPurgeHistoryBackground } from './main/layouts/hooks/useHistoryTracker';
|
||||
|
||||
@@ -53,17 +54,6 @@ function MaintenancePage() {
|
||||
);
|
||||
}
|
||||
|
||||
function ComingSoonPage() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<StatusPage
|
||||
title={t('common:systemPages.comingSoon.title')}
|
||||
heading={t('common:systemPages.comingSoon.heading')}
|
||||
description={t('common:systemPages.comingSoon.description')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{ path: '/auth/*', element: <AuthModule /> },
|
||||
{ path: '/app/*', element: <AppModule /> },
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import navEn from '../languages/en/nav.json';
|
||||
import navId from '../languages/id/nav.json';
|
||||
import { MENU_ITEMS } from './menu.data';
|
||||
import type { MenuItemType } from '../types/menu.types';
|
||||
|
||||
const childKeys = (item: MenuItemType | undefined): string[] => (item?.children ?? []).map((child) => child.key);
|
||||
|
||||
const findItem = (items: MenuItemType[], key: string): MenuItemType | undefined => items.find((item) => item.key === key);
|
||||
|
||||
const flatten = (items: MenuItemType[]): MenuItemType[] =>
|
||||
items.flatMap((item) => [item, ...(item.children ? flatten(item.children) : [])]);
|
||||
|
||||
describe('MENU_ITEMS', () => {
|
||||
it('orders top-level items as dashboard, sales, logistics, settings', () => {
|
||||
expect(MENU_ITEMS.map((item) => item.key)).toEqual(['dashboard', 'sales', 'logistics', 'settings']);
|
||||
});
|
||||
|
||||
it('nests sales as data, activities, then reports', () => {
|
||||
const sales = findItem(MENU_ITEMS, 'sales');
|
||||
|
||||
expect(childKeys(sales)).toEqual(['sales-data', 'sales-activities', 'sales-reports']);
|
||||
expect(childKeys(findItem(sales?.children ?? [], 'sales-data'))).toEqual(['sales-employees', 'sales-cycles']);
|
||||
expect(childKeys(findItem(sales?.children ?? [], 'sales-activities'))).toEqual([
|
||||
'sales-requests',
|
||||
'sales-orders',
|
||||
'sales-invoices',
|
||||
'sales-payments',
|
||||
'sales-plans',
|
||||
]);
|
||||
});
|
||||
|
||||
it('nests logistics as data, activities, then reports', () => {
|
||||
const logistics = findItem(MENU_ITEMS, 'logistics');
|
||||
|
||||
expect(childKeys(logistics)).toEqual(['logistics-data', 'logistics-activities', 'logistics-reports']);
|
||||
expect(childKeys(findItem(logistics?.children ?? [], 'logistics-data'))).toEqual([
|
||||
'logistics-employees',
|
||||
'logistics-cycles',
|
||||
]);
|
||||
expect(childKeys(findItem(logistics?.children ?? [], 'logistics-activities'))).toEqual([
|
||||
'logistics-packing-slips',
|
||||
'logistics-plans',
|
||||
]);
|
||||
});
|
||||
|
||||
it('nests settings as data then user', () => {
|
||||
const settings = findItem(MENU_ITEMS, 'settings');
|
||||
|
||||
expect(childKeys(settings)).toEqual(['settings-data', 'settings-user']);
|
||||
expect(childKeys(findItem(settings?.children ?? [], 'settings-data'))).toEqual([
|
||||
'configuration-branches',
|
||||
'configuration-divisions',
|
||||
'configuration-customers',
|
||||
'configuration-products',
|
||||
]);
|
||||
expect(childKeys(findItem(settings?.children ?? [], 'settings-user'))).toEqual(['system-users', 'system-privileges']);
|
||||
});
|
||||
|
||||
it('uses unique keys across the whole tree', () => {
|
||||
const keys = flatten(MENU_ITEMS).map((item) => item.key);
|
||||
expect(new Set(keys).size).toBe(keys.length);
|
||||
});
|
||||
|
||||
it('resolves every label in both locales', () => {
|
||||
for (const item of flatten(MENU_ITEMS)) {
|
||||
const key = item.label.replace('nav:', '');
|
||||
expect(navEn).toHaveProperty(key);
|
||||
expect(navId).toHaveProperty(key);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -7,24 +7,13 @@ import {
|
||||
Users,
|
||||
Box,
|
||||
Layers,
|
||||
Warehouse,
|
||||
Building2,
|
||||
MapPin,
|
||||
Activity,
|
||||
Globe,
|
||||
Briefcase,
|
||||
Phone,
|
||||
ShoppingCart,
|
||||
Truck,
|
||||
HardHat,
|
||||
Factory,
|
||||
Calculator,
|
||||
Receipt,
|
||||
PiggyBank,
|
||||
Calendar,
|
||||
Clock,
|
||||
Shield,
|
||||
FileSearch,
|
||||
Repeat,
|
||||
Package,
|
||||
ClipboardList,
|
||||
@@ -44,77 +33,83 @@ export const MENU_ITEMS: MenuItemType[] = [
|
||||
icon: LayoutDashboard,
|
||||
path: '/app/dashboard',
|
||||
},
|
||||
// {
|
||||
// key: 'crm',
|
||||
// label: 'nav:crm',
|
||||
// icon: Users,
|
||||
// path: '/app/crm',
|
||||
// children: [
|
||||
// {
|
||||
// key: 'crm-leads',
|
||||
// label: 'nav:crm-leads',
|
||||
// icon: Briefcase,
|
||||
// path: '/app/crm/leads',
|
||||
// },
|
||||
// {
|
||||
// key: 'crm-pipelines',
|
||||
// label: 'nav:crm-pipelines',
|
||||
// icon: Activity,
|
||||
// path: '/app/crm/pipelines',
|
||||
// },
|
||||
// {
|
||||
// key: 'crm-contacts',
|
||||
// label: 'nav:crm-contacts',
|
||||
// icon: Phone,
|
||||
// path: '/app/crm/contacts',
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
key: 'sales',
|
||||
label: 'nav:sales',
|
||||
icon: ShoppingCart,
|
||||
path: '/app/sales',
|
||||
children: [
|
||||
// {
|
||||
// key: 'sales-quotations',
|
||||
// label: 'nav:sales-quotations',
|
||||
// icon: FileText,
|
||||
// path: '/app/sales/quotations',
|
||||
// },
|
||||
{
|
||||
key: 'sales-requests',
|
||||
label: 'nav:sales-requests',
|
||||
icon: ClipboardList,
|
||||
path: '/app/sales/requests/index',
|
||||
moduleKey: 'SALES.REQUEST',
|
||||
key: 'sales-data',
|
||||
label: 'nav:data',
|
||||
icon: Database,
|
||||
path: '/app/sales/data',
|
||||
children: [
|
||||
{
|
||||
key: 'sales-employees',
|
||||
label: 'nav:configuration-employees',
|
||||
icon: Users,
|
||||
path: '/app/configuration/employees/index',
|
||||
moduleKey: 'CONFIGURATION.EMPLOYEE',
|
||||
},
|
||||
{
|
||||
key: 'sales-cycles',
|
||||
label: 'nav:sales-cycles',
|
||||
icon: Repeat,
|
||||
path: '/app/sales/cycles/index',
|
||||
moduleKey: 'SALES.CYCLE',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'sales-orders',
|
||||
label: 'nav:sales-orders',
|
||||
icon: Box,
|
||||
path: '/app/sales/orders/index',
|
||||
moduleKey: 'SALES.ORDER',
|
||||
key: 'sales-activities',
|
||||
label: 'nav:activities',
|
||||
icon: Activity,
|
||||
path: '/app/sales/activities',
|
||||
children: [
|
||||
{
|
||||
key: 'sales-requests',
|
||||
label: 'nav:sales-requests',
|
||||
icon: ClipboardList,
|
||||
path: '/app/sales/requests/index',
|
||||
moduleKey: 'SALES.REQUEST',
|
||||
},
|
||||
{
|
||||
key: 'sales-orders',
|
||||
label: 'nav:sales-orders',
|
||||
icon: Box,
|
||||
path: '/app/sales/orders/index',
|
||||
moduleKey: 'SALES.ORDER',
|
||||
},
|
||||
{
|
||||
key: 'sales-invoices',
|
||||
label: 'nav:sales-invoices',
|
||||
icon: Receipt,
|
||||
path: '/app/sales/invoices',
|
||||
moduleKey: 'SALES.INVOICE',
|
||||
},
|
||||
{
|
||||
key: 'sales-payments',
|
||||
label: 'nav:sales-payments',
|
||||
icon: CreditCard,
|
||||
path: '/app/sales/payments',
|
||||
moduleKey: 'SALES.PAYMENT',
|
||||
},
|
||||
{
|
||||
key: 'sales-plans',
|
||||
label: 'nav:sales-plans',
|
||||
icon: Calendar,
|
||||
path: '/app/sales/plans/index',
|
||||
moduleKey: 'SALES.PLAN',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'sales-invoices',
|
||||
label: 'nav:sales-invoices',
|
||||
icon: Receipt,
|
||||
path: '/app/sales/invoices',
|
||||
},
|
||||
{
|
||||
key: 'sales-cycles',
|
||||
label: 'nav:sales-cycles',
|
||||
icon: Repeat,
|
||||
path: '/app/sales/cycles/index',
|
||||
moduleKey: 'SALES.CYCLE',
|
||||
},
|
||||
{
|
||||
key: 'sales-plans',
|
||||
label: 'nav:sales-plans',
|
||||
icon: Calendar,
|
||||
path: '/app/sales/plans/index',
|
||||
moduleKey: 'SALES.PLAN',
|
||||
key: 'sales-reports',
|
||||
label: 'nav:reports-coming-soon',
|
||||
icon: FileText,
|
||||
path: '/app/sales/reports',
|
||||
isPlaceholder: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -125,249 +120,122 @@ export const MENU_ITEMS: MenuItemType[] = [
|
||||
path: '/app/logistics',
|
||||
children: [
|
||||
{
|
||||
key: 'logistics-cycles',
|
||||
label: 'nav:logistics-cycles',
|
||||
icon: Repeat,
|
||||
path: '/app/logistics/cycles/index',
|
||||
moduleKey: 'LOGISTICS.CYCLE',
|
||||
key: 'logistics-data',
|
||||
label: 'nav:data',
|
||||
icon: Database,
|
||||
path: '/app/logistics/data',
|
||||
children: [
|
||||
{
|
||||
key: 'logistics-employees',
|
||||
label: 'nav:configuration-employees',
|
||||
icon: Users,
|
||||
path: '/app/configuration/employees/index',
|
||||
moduleKey: 'CONFIGURATION.EMPLOYEE',
|
||||
},
|
||||
{
|
||||
key: 'logistics-cycles',
|
||||
label: 'nav:logistics-cycles',
|
||||
icon: Repeat,
|
||||
path: '/app/logistics/cycles/index',
|
||||
moduleKey: 'LOGISTICS.CYCLE',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'logistics-plans',
|
||||
label: 'nav:logistics-plans',
|
||||
icon: Calendar,
|
||||
path: '/app/logistics/plans/index',
|
||||
moduleKey: 'LOGISTICS.PLAN',
|
||||
},
|
||||
],
|
||||
},
|
||||
// {
|
||||
// key: 'supply-chain',
|
||||
// label: 'nav:supply-chain',
|
||||
// icon: Truck,
|
||||
// path: '/app/supply-chain',
|
||||
// children: [
|
||||
// {
|
||||
// key: 'sc-inventory',
|
||||
// label: 'nav:sc-inventory',
|
||||
// icon: Box,
|
||||
// path: '/app/supply-chain/inventory',
|
||||
// children: [
|
||||
// {
|
||||
// key: 'sc-inventory-products',
|
||||
// label: 'nav:sc-inventory-products',
|
||||
// icon: Layers,
|
||||
// path: '/app/supply-chain/inventory/products',
|
||||
// },
|
||||
// {
|
||||
// key: 'sc-inventory-categories',
|
||||
// label: 'nav:sc-inventory-categories',
|
||||
// icon: Globe,
|
||||
// path: '/app/supply-chain/inventory/categories',
|
||||
// },
|
||||
// {
|
||||
// key: 'sc-inventory-adjustments',
|
||||
// label: 'nav:sc-inventory-adjustments',
|
||||
// icon: FileSearch,
|
||||
// path: '/app/supply-chain/inventory/adjustments',
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// key: 'sc-warehouses',
|
||||
// label: 'nav:sc-warehouses',
|
||||
// icon: Warehouse,
|
||||
// path: '/app/supply-chain/warehouses',
|
||||
// },
|
||||
// {
|
||||
// key: 'sc-logistics',
|
||||
// label: 'nav:sc-logistics',
|
||||
// icon: Globe,
|
||||
// path: '/app/supply-chain/logistics',
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// key: 'manufacturing',
|
||||
// label: 'nav:manufacturing',
|
||||
// icon: Factory,
|
||||
// path: '/app/manufacturing',
|
||||
// children: [
|
||||
// {
|
||||
// key: 'mfg-bom',
|
||||
// label: 'nav:mfg-bom',
|
||||
// icon: Layers,
|
||||
// path: '/app/manufacturing/bom',
|
||||
// },
|
||||
// {
|
||||
// key: 'mfg-work-orders',
|
||||
// label: 'nav:mfg-work-orders',
|
||||
// icon: HardHat,
|
||||
// path: '/app/manufacturing/work-orders',
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// key: 'hris',
|
||||
// label: 'nav:hris',
|
||||
// icon: Briefcase,
|
||||
// path: '/app/hris',
|
||||
// children: [
|
||||
// {
|
||||
// key: 'hris-employees',
|
||||
// label: 'nav:hris-employees',
|
||||
// icon: Users,
|
||||
// path: '/app/hris/employees',
|
||||
// },
|
||||
// {
|
||||
// key: 'hris-attendance',
|
||||
// label: 'nav:hris-attendance',
|
||||
// icon: Clock,
|
||||
// path: '/app/hris/attendance',
|
||||
// },
|
||||
// {
|
||||
// key: 'hris-payroll',
|
||||
// label: 'nav:hris-payroll',
|
||||
// icon: CreditCard,
|
||||
// path: '/app/hris/payroll',
|
||||
// },
|
||||
// {
|
||||
// key: 'hris-calendar',
|
||||
// label: 'nav:hris-calendar',
|
||||
// icon: Calendar,
|
||||
// path: '/app/hris/calendar',
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// key: 'accounting',
|
||||
// label: 'nav:accounting',
|
||||
// icon: Calculator,
|
||||
// path: '/app/accounting',
|
||||
// children: [
|
||||
// {
|
||||
// key: 'acc-gl',
|
||||
// label: 'nav:acc-gl',
|
||||
// icon: Database,
|
||||
// path: '/app/accounting/general-ledger',
|
||||
// },
|
||||
// {
|
||||
// key: 'acc-taxes',
|
||||
// label: 'nav:acc-taxes',
|
||||
// icon: PiggyBank,
|
||||
// path: '/app/accounting/taxes',
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// key: 'settings',
|
||||
// label: 'nav:settings',
|
||||
// icon: Settings,
|
||||
// path: '/app/settings',
|
||||
// children: [
|
||||
// {
|
||||
// key: 'settings-general',
|
||||
// label: 'nav:settings-general',
|
||||
// icon: Settings,
|
||||
// path: '/app/settings/general',
|
||||
// },
|
||||
// {
|
||||
// key: 'settings-security',
|
||||
// label: 'nav:settings-security',
|
||||
// icon: Shield,
|
||||
// path: '/app/settings/security',
|
||||
// },
|
||||
// {
|
||||
// key: 'long-text-2',
|
||||
// label: 'nav:long-text-2',
|
||||
// icon: FileText,
|
||||
// path: '/app/settings/long-menu-test',
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
key: 'configuration',
|
||||
label: 'nav:configuration',
|
||||
icon: Building2,
|
||||
path: '/app/configuration',
|
||||
children: [
|
||||
{
|
||||
key: 'configuration-divisions',
|
||||
label: 'nav:configuration-divisions',
|
||||
icon: Layers,
|
||||
path: '/app/configuration/divisions/index',
|
||||
moduleKey: 'CONFIGURATION.DIVISION',
|
||||
key: 'logistics-activities',
|
||||
label: 'nav:activities',
|
||||
icon: Activity,
|
||||
path: '/app/logistics/activities',
|
||||
children: [
|
||||
{
|
||||
key: 'logistics-packing-slips',
|
||||
label: 'nav:logistics-packing-slips',
|
||||
icon: Package,
|
||||
path: '/app/logistics/packing-slips',
|
||||
moduleKey: 'SALES.PACKING_SLIP',
|
||||
},
|
||||
{
|
||||
key: 'logistics-plans',
|
||||
label: 'nav:logistics-plans',
|
||||
icon: Calendar,
|
||||
path: '/app/logistics/plans/index',
|
||||
moduleKey: 'LOGISTICS.PLAN',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'configuration-branches',
|
||||
label: 'nav:configuration-branches',
|
||||
icon: MapPin,
|
||||
path: '/app/configuration/branches/index',
|
||||
moduleKey: 'CONFIGURATION.BRANCH',
|
||||
},
|
||||
{
|
||||
key: 'configuration-customers',
|
||||
label: 'nav:configuration-customers',
|
||||
icon: Users,
|
||||
path: '/app/configuration/customers/index',
|
||||
moduleKey: 'CONFIGURATION.CUSTOMER',
|
||||
},
|
||||
{
|
||||
key: 'configuration-employees',
|
||||
label: 'nav:configuration-employees',
|
||||
icon: Users,
|
||||
path: '/app/configuration/employees/index',
|
||||
moduleKey: 'CONFIGURATION.EMPLOYEE',
|
||||
},
|
||||
{
|
||||
key: 'configuration-products',
|
||||
label: 'nav:configuration-products',
|
||||
icon: Package,
|
||||
path: '/app/configuration/products/index',
|
||||
moduleKey: 'CONFIGURATION.PRODUCT',
|
||||
key: 'logistics-reports',
|
||||
label: 'nav:reports-coming-soon',
|
||||
icon: FileText,
|
||||
path: '/app/logistics/reports',
|
||||
isPlaceholder: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'system-group',
|
||||
label: 'nav:system',
|
||||
icon: Shield,
|
||||
path: '/app/system',
|
||||
key: 'settings',
|
||||
label: 'nav:settings',
|
||||
icon: Settings,
|
||||
path: '/app/settings',
|
||||
children: [
|
||||
{
|
||||
key: 'system-privileges',
|
||||
label: 'nav:system-privileges',
|
||||
icon: Shield,
|
||||
path: '/app/system/privileges/index',
|
||||
moduleKey: 'PRIVILEGES',
|
||||
key: 'settings-data',
|
||||
label: 'nav:data',
|
||||
icon: Database,
|
||||
path: '/app/settings/data',
|
||||
children: [
|
||||
{
|
||||
key: 'configuration-branches',
|
||||
label: 'nav:configuration-branches',
|
||||
icon: MapPin,
|
||||
path: '/app/configuration/branches/index',
|
||||
moduleKey: 'CONFIGURATION.BRANCH',
|
||||
},
|
||||
{
|
||||
key: 'configuration-divisions',
|
||||
label: 'nav:configuration-divisions',
|
||||
icon: Layers,
|
||||
path: '/app/configuration/divisions/index',
|
||||
moduleKey: 'CONFIGURATION.DIVISION',
|
||||
},
|
||||
{
|
||||
key: 'configuration-customers',
|
||||
label: 'nav:configuration-customers',
|
||||
icon: Users,
|
||||
path: '/app/configuration/customers/index',
|
||||
moduleKey: 'CONFIGURATION.CUSTOMER',
|
||||
},
|
||||
{
|
||||
key: 'configuration-products',
|
||||
label: 'nav:configuration-products',
|
||||
icon: Package,
|
||||
path: '/app/configuration/products/index',
|
||||
moduleKey: 'CONFIGURATION.PRODUCT',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'system-users',
|
||||
label: 'nav:system-users',
|
||||
key: 'settings-user',
|
||||
label: 'nav:user',
|
||||
icon: Users,
|
||||
path: '/app/system/users/index',
|
||||
moduleKey: 'USERS',
|
||||
path: '/app/settings/user',
|
||||
children: [
|
||||
{
|
||||
key: 'system-users',
|
||||
label: 'nav:system-users',
|
||||
icon: Users,
|
||||
path: '/app/system/users/index',
|
||||
moduleKey: 'USERS',
|
||||
},
|
||||
{
|
||||
key: 'system-privileges',
|
||||
label: 'nav:system-privileges',
|
||||
icon: Shield,
|
||||
path: '/app/system/privileges/index',
|
||||
moduleKey: 'PRIVILEGES',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// {
|
||||
// key: 'example-module',
|
||||
// label: 'nav:example-module',
|
||||
// icon: Database,
|
||||
// path: '/app/example-module',
|
||||
// children: [
|
||||
// {
|
||||
// key: 'example-full-page',
|
||||
// label: 'nav:example-full-page',
|
||||
// icon: LayoutDashboard,
|
||||
// path: '/app/example/full-page/index',
|
||||
// moduleKey: 'EXAMPLE_FULL_PAGE',
|
||||
// },
|
||||
// // {
|
||||
// // key: 'example-single-page',
|
||||
// // label: 'nav:example-single-page',
|
||||
// // icon: FileText,
|
||||
// // path: '/app/example/single-page/index',
|
||||
// // },
|
||||
// ],
|
||||
// },
|
||||
];
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"sales-quotations": "Quotations",
|
||||
"sales-requests": "Sales Requests",
|
||||
"sales-orders": "Sales Orders",
|
||||
"sales-invoices": "Invoices",
|
||||
"sales-invoices": "Sales Invoices",
|
||||
"sales-payments": "Sales Payments",
|
||||
"supply-chain": "Supply Chain",
|
||||
"sc-inventory": "Inventory Management",
|
||||
"sc-inventory-products": "Products",
|
||||
@@ -27,7 +28,11 @@
|
||||
"accounting": "Accounting",
|
||||
"acc-gl": "General Ledger",
|
||||
"acc-taxes": "Taxes",
|
||||
"settings": "Settings & Configuration",
|
||||
"data": "Data",
|
||||
"activities": "Activities",
|
||||
"reports-coming-soon": "Reports (Coming Soon)",
|
||||
"user": "User",
|
||||
"settings": "Settings",
|
||||
"settings-general": "General Settings",
|
||||
"settings-security": "Security",
|
||||
"long-text-2": "Extremely Long Menu Name To Test Text Truncation Handling Properly",
|
||||
@@ -46,6 +51,7 @@
|
||||
"logistics": "Logistics",
|
||||
"logistics-cycles": "Logistics Cycles",
|
||||
"logistics-plans": "Logistics Plans",
|
||||
"logistics-packing-slips": "Packing Slips",
|
||||
"configuration-employees": "Employees",
|
||||
"configuration-products": "Products"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"sales-quotations": "Penawaran",
|
||||
"sales-requests": "Permintaan Penjualan",
|
||||
"sales-orders": "Pesanan Penjualan",
|
||||
"sales-invoices": "Faktur",
|
||||
"sales-invoices": "Faktur Penjualan",
|
||||
"sales-payments": "Pembayaran Penjualan",
|
||||
"supply-chain": "Rantai Pasok",
|
||||
"sc-inventory": "Manajemen Inventaris",
|
||||
"sc-inventory-products": "Produk",
|
||||
@@ -27,7 +28,11 @@
|
||||
"accounting": "Akuntansi",
|
||||
"acc-gl": "Buku Besar",
|
||||
"acc-taxes": "Pajak",
|
||||
"settings": "Pengaturan & Konfigurasi",
|
||||
"data": "Data",
|
||||
"activities": "Aktivitas",
|
||||
"reports-coming-soon": "Laporan (Segera Hadir)",
|
||||
"user": "Pengguna",
|
||||
"settings": "Pengaturan",
|
||||
"settings-general": "Pengaturan Umum",
|
||||
"settings-security": "Keamanan",
|
||||
"long-text-2": "Nama Menu Sangat Panjang Untuk Menguji Penanganan Pemotongan Teks Dengan Baik",
|
||||
@@ -46,6 +51,7 @@
|
||||
"logistics": "Logistik",
|
||||
"logistics-cycles": "Siklus Logistik",
|
||||
"logistics-plans": "Rencana Logistik",
|
||||
"logistics-packing-slips": "Surat Jalan",
|
||||
"configuration-employees": "Karyawan",
|
||||
"configuration-products": "Produk"
|
||||
}
|
||||
|
||||
@@ -16,4 +16,6 @@ export interface MenuItemType {
|
||||
children?: MenuItemType[];
|
||||
/** Privilege catalog key used to hide the item when ALLOW_VIEW is false */
|
||||
moduleKey?: string;
|
||||
/** Coming-soon leaf that must not keep an otherwise-empty parent visible */
|
||||
isPlaceholder?: boolean;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { lazy } from 'react';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
import { EmbeddedComingSoonPage } from '../../../../../core/components/coming-soon-page';
|
||||
|
||||
const CyclesModule = lazy(() => import('../cycles/presentation/factory'));
|
||||
const PlansModule = lazy(() => import('../plans/presentation/factory'));
|
||||
@@ -9,6 +10,8 @@ export default function LogisticsFieldModule() {
|
||||
<Routes>
|
||||
<Route path="/cycles/*" element={<CyclesModule purpose="logistics" />} />
|
||||
<Route path="/plans/*" element={<PlansModule purpose="logistics" />} />
|
||||
<Route path="/packing-slips" element={<EmbeddedComingSoonPage />} />
|
||||
<Route path="/reports" element={<EmbeddedComingSoonPage />} />
|
||||
<Route path="*" element={<Navigate to={'/404'} replace={true} />} />
|
||||
</Routes>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { lazy } from 'react';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
import { EmbeddedComingSoonPage } from '../../../../core/components/coming-soon-page';
|
||||
|
||||
const RequestsModule = lazy(() => import('./requests/presentation/factory'));
|
||||
const OrdersModule = lazy(() => import('./orders/presentation/factory'));
|
||||
@@ -13,6 +14,9 @@ export default function SalesModule() {
|
||||
<Route path="/orders/*" element={<OrdersModule />} />
|
||||
<Route path="/cycles/*" element={<CyclesModule purpose="sales" />} />
|
||||
<Route path="/plans/*" element={<PlansModule purpose="sales" />} />
|
||||
<Route path="/invoices" element={<EmbeddedComingSoonPage />} />
|
||||
<Route path="/payments" element={<EmbeddedComingSoonPage />} />
|
||||
<Route path="/reports" element={<EmbeddedComingSoonPage />} />
|
||||
<Route path="*" element={<Navigate to={'/404'} replace={true} />} />
|
||||
</Routes>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { StatusPage } from '@repo/ui/components';
|
||||
import { useTranslation } from '@repo/core-i18n';
|
||||
|
||||
interface ComingSoonPageProps {
|
||||
/** Use inside ModuleLayout so the page fits under the app chrome */
|
||||
embedded?: boolean;
|
||||
}
|
||||
|
||||
export function ComingSoonPage({ embedded = false }: ComingSoonPageProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<StatusPage
|
||||
title={t('common:systemPages.comingSoon.title')}
|
||||
heading={t('common:systemPages.comingSoon.heading')}
|
||||
description={t('common:systemPages.comingSoon.description')}
|
||||
showActionsBack={!embedded}
|
||||
showActionsHome
|
||||
homeUrl="/app"
|
||||
backButtonLabel={t('common:systemPages.actions.goBack')}
|
||||
homeButtonLabel={t('common:systemPages.actions.backToHome')}
|
||||
height={embedded ? 500 : undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function EmbeddedComingSoonPage() {
|
||||
return <ComingSoonPage embedded />;
|
||||
}
|
||||
@@ -2,7 +2,15 @@ import { describe, expect, it } from 'vitest';
|
||||
import { noPrivileges } from '@repo/ui/foundations';
|
||||
import { filterMenuByViewPrivilege } from './filter-menu-by-view-privilege';
|
||||
|
||||
const items = [
|
||||
type TestMenuItem = {
|
||||
key: string;
|
||||
path?: string;
|
||||
moduleKey?: string;
|
||||
isPlaceholder?: boolean;
|
||||
children?: TestMenuItem[];
|
||||
};
|
||||
|
||||
const items: TestMenuItem[] = [
|
||||
{
|
||||
key: 'system',
|
||||
children: [
|
||||
@@ -58,4 +66,43 @@ describe('filterMenuByViewPrivilege', () => {
|
||||
const filtered = filterMenuByViewPrivilege(items, {}, false);
|
||||
expect(filtered.find((item) => item.key === 'dashboard')).toEqual({ key: 'dashboard', path: '/app/dashboard' });
|
||||
});
|
||||
|
||||
it('drops a parent when the only remaining children are placeholders', () => {
|
||||
const menu: TestMenuItem[] = [
|
||||
{
|
||||
key: 'sales',
|
||||
children: [
|
||||
{ key: 'orders', moduleKey: 'SALES.ORDER', path: '/app/sales/orders/index' },
|
||||
{ key: 'reports', path: '/app/sales/reports', isPlaceholder: true },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const filtered = filterMenuByViewPrivilege(menu, { 'SALES.ORDER': { ...noPrivileges } }, false);
|
||||
|
||||
expect(filtered.find((item) => item.key === 'sales')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('keeps placeholders when a real sibling remains', () => {
|
||||
const menu: TestMenuItem[] = [
|
||||
{
|
||||
key: 'sales',
|
||||
children: [
|
||||
{ key: 'orders', moduleKey: 'SALES.ORDER', path: '/app/sales/orders/index' },
|
||||
{ key: 'reports', path: '/app/sales/reports', isPlaceholder: true },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const filtered = filterMenuByViewPrivilege(
|
||||
menu,
|
||||
{ 'SALES.ORDER': { ...noPrivileges, ALLOW_VIEW: true } },
|
||||
false,
|
||||
);
|
||||
|
||||
expect(filtered.find((item) => item.key === 'sales')?.children?.map((child) => child.key)).toEqual([
|
||||
'orders',
|
||||
'reports',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import type { PrivilegeEntity } from '@repo/ui/foundations';
|
||||
|
||||
export function filterMenuByViewPrivilege<T extends { moduleKey?: string; children?: T[] }>(
|
||||
items: T[],
|
||||
privileges: Record<string, PrivilegeEntity>,
|
||||
isSuperadmin: boolean,
|
||||
): T[] {
|
||||
export function filterMenuByViewPrivilege<
|
||||
T extends { moduleKey?: string; children?: T[]; isPlaceholder?: boolean },
|
||||
>(items: T[], privileges: Record<string, PrivilegeEntity>, isSuperadmin: boolean): T[] {
|
||||
if (isSuperadmin) {
|
||||
return items;
|
||||
}
|
||||
@@ -17,7 +15,8 @@ export function filterMenuByViewPrivilege<T extends { moduleKey?: string; childr
|
||||
}
|
||||
|
||||
if (children) {
|
||||
if (children.length === 0) {
|
||||
const hasRealChild = children.some((child) => !child.isPlaceholder);
|
||||
if (!hasRealChild) {
|
||||
return [];
|
||||
}
|
||||
return [{ ...item, children }];
|
||||
|
||||
Reference in New Issue
Block a user