From a2616f2b2da1bd1a1665c68d41b4aaf0d5330422 Mon Sep 17 00:00:00 2001
From: Firman Ramdhani <33869609+firmanramdhani@users.noreply.github.com>
Date: Mon, 27 Jul 2026 16:44:05 +0700
Subject: [PATCH 1/3] feat: refine sidebar icon styling
---
.../src/apps/modules/layouts/components/sidebar.tsx | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/apps/web/src/apps/modules/layouts/components/sidebar.tsx b/apps/web/src/apps/modules/layouts/components/sidebar.tsx
index 00693b0..3fb7189 100644
--- a/apps/web/src/apps/modules/layouts/components/sidebar.tsx
+++ b/apps/web/src/apps/modules/layouts/components/sidebar.tsx
@@ -136,7 +136,7 @@ const MenuItemFlyout = memo(function MenuItemFlyout({ item, activeKeys, isRoot =
size="lg"
aria-label={t(item.label)}
>
-
+
);
@@ -170,7 +170,7 @@ const MenuItemFlyout = memo(function MenuItemFlyout({ item, activeKeys, isRoot =
size="lg"
aria-label={t(item.label)}
>
-
+
) : (
- {isAllExpanded ? : }
+ {isAllExpanded ? (
+
+ ) : (
+
+ )}
From 75e0424e36ff7946a63b31859354e0db79d3d910 Mon Sep 17 00:00:00 2001
From: Firman Ramdhani <33869609+firmanramdhani@users.noreply.github.com>
Date: Mon, 27 Jul 2026 17:02:58 +0700
Subject: [PATCH 2/3] feat: implement persistent AG Grid column state
---
.../full-page/presentation/store/index.ts | 3 +
.../components/data-table/index.tsx | 77 ++++++++++++++++++-
.../enterprise-module/entities/entity.ts | 5 ++
.../providers/module.provider.tsx | 6 +-
4 files changed, 87 insertions(+), 4 deletions(-)
diff --git a/apps/web/src/apps/modules/example/full-page/presentation/store/index.ts b/apps/web/src/apps/modules/example/full-page/presentation/store/index.ts
index d347059..f55dce7 100644
--- a/apps/web/src/apps/modules/example/full-page/presentation/store/index.ts
+++ b/apps/web/src/apps/modules/example/full-page/presentation/store/index.ts
@@ -16,4 +16,7 @@ export const fullPageStore = create((set) => ({
privileges: [],
setPrivileges: (privileges) => set({ privileges }),
+
+ tableConfig: null,
+ setTableConfig: (config) => set({ tableConfig: config }),
}));
diff --git a/packages/ui/src/foundations/enterprise-module/components/data-table/index.tsx b/packages/ui/src/foundations/enterprise-module/components/data-table/index.tsx
index f98ba8f..c19676b 100644
--- a/packages/ui/src/foundations/enterprise-module/components/data-table/index.tsx
+++ b/packages/ui/src/foundations/enterprise-module/components/data-table/index.tsx
@@ -10,6 +10,10 @@ import {
MenuItemDef,
DefaultMenuItem,
StatusBar,
+ ColumnMovedEvent,
+ ColumnResizedEvent,
+ ColumnVisibleEvent,
+ SortChangedEvent,
} from 'ag-grid-community';
import { Box, Group, TextInput, Indicator, CloseButton } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
@@ -181,6 +185,12 @@ export function EnterpriseDataTable(props: EnterpriseDataT
searchKey = 'search',
filterConfig,
+ // Grid config props
+ onColumnMoved,
+ onColumnResized,
+ onColumnVisible,
+ onSortChanged,
+
// Bulk action props
customBulkActions,
batchSize,
@@ -200,8 +210,16 @@ export function EnterpriseDataTable(props: EnterpriseDataT
// ---------------------------------------------------------------------------
const { t } = useEnterpriseModuleTranslationContext();
const { dataServices } = useEnterpriseModuleDataServiceContext();
- const { selectedRows, setSelectedRows, metaData, setMetaData, filterData, setFilterData } =
- useEnterpriseModuleSelectionContext();
+ const {
+ selectedRows,
+ setSelectedRows,
+ metaData,
+ setMetaData,
+ filterData,
+ setFilterData,
+ tableConfig,
+ setTableConfig,
+ } = useEnterpriseModuleSelectionContext();
const navigation = useEnterpriseModuleNavigationContext();
const { config } = useEnterpriseModuleConfigContext();
@@ -791,6 +809,11 @@ export function EnterpriseDataTable(props: EnterpriseDataT
setSelectedRows([]);
if (params.api) {
+ // Restore grid configuration on mount
+ if (tableConfig) {
+ params.api.applyColumnState({ state: tableConfig, applyOrder: true });
+ }
+
// Attach the server-side datasource to the grid API
params.api.setGridOption('serverSideDatasource', datasource);
@@ -803,7 +826,7 @@ export function EnterpriseDataTable(props: EnterpriseDataT
}
}
},
- [datasource, setSelectedRows, isPaginated, metaData],
+ [datasource, setSelectedRows, isPaginated, metaData, tableConfig],
);
// Triggered whenever the row selection in the grid changes
@@ -817,6 +840,50 @@ export function EnterpriseDataTable(props: EnterpriseDataT
[setSelectedRows],
);
+ // ---------------------------------------------------------------------------
+ // Save Grid Configuration State Handlers
+ // ---------------------------------------------------------------------------
+ const saveGridState = useCallback(() => {
+ if (!gridApiRef.current || !setTableConfig) return;
+ const state = gridApiRef.current.getColumnState();
+ setTableConfig(state);
+ }, [setTableConfig]);
+
+ const handleColumnMoved = useCallback(
+ (e: ColumnMovedEvent) => {
+ saveGridState();
+ if (onColumnMoved) onColumnMoved(e);
+ },
+ [saveGridState, onColumnMoved],
+ );
+
+ const handleColumnResized = useCallback(
+ (e: ColumnResizedEvent) => {
+ // Only save when resizing is finished to prevent excessive re-renders
+ if (e.finished) {
+ saveGridState();
+ }
+ if (onColumnResized) onColumnResized(e);
+ },
+ [saveGridState, onColumnResized],
+ );
+
+ const handleColumnVisible = useCallback(
+ (e: ColumnVisibleEvent) => {
+ saveGridState();
+ if (onColumnVisible) onColumnVisible(e);
+ },
+ [saveGridState, onColumnVisible],
+ );
+
+ const handleSortChanged = useCallback(
+ (e: SortChangedEvent) => {
+ saveGridState();
+ if (onSortChanged) onSortChanged(e);
+ },
+ [saveGridState, onSortChanged],
+ );
+
// Configures the context menu items available when right-clicking a cell
const getContextMenuItems = useCallback(():
| (DefaultMenuItem | MenuItemDef)[]
@@ -923,6 +990,10 @@ export function EnterpriseDataTable(props: EnterpriseDataT
enableCellTextSelection={true}
onSelectionChanged={handleSelectionChanged}
onGridReady={onGridReady}
+ onColumnMoved={handleColumnMoved}
+ onColumnResized={handleColumnResized}
+ onColumnVisible={handleColumnVisible}
+ onSortChanged={handleSortChanged}
getContextMenuItems={getContextMenuItems}
statusBar={statusBar}
domLayout={domLayout}
diff --git a/packages/ui/src/foundations/enterprise-module/entities/entity.ts b/packages/ui/src/foundations/enterprise-module/entities/entity.ts
index 94f4a0b..711b43a 100644
--- a/packages/ui/src/foundations/enterprise-module/entities/entity.ts
+++ b/packages/ui/src/foundations/enterprise-module/entities/entity.ts
@@ -4,6 +4,7 @@ import type { ZodType } from 'zod';
import type { BaseEntity, BaseRemoteDataServices, StandardPaginationMeta } from '@repo/core-api/data-services';
import type { ModulePageHeaderProps } from '../components/module-page-header';
import { PageActionsProps } from '../../../components';
+import { ColumnState } from 'ag-grid-community';
// ---------------------------------------------------------------------------
// Module Constants & Base Types
@@ -140,6 +141,8 @@ export interface SelectionSlice<
setMetaData: (data: TMeta | null) => void;
filterData: TFilter | null;
setFilterData: (data: TFilter | null) => void;
+ tableConfig: ColumnState[] | null;
+ setTableConfig: (config: ColumnState[] | null) => void;
}
/**
@@ -156,6 +159,8 @@ export interface EnterpriseModuleState<
setFilterData: (data: TFilter | null) => void;
selectedRows: E[];
setSelectedRows: (rows: E[]) => void;
+ tableConfig: ColumnState[] | null;
+ setTableConfig: (config: ColumnState[] | null) => void;
privileges: string[];
setPrivileges: (privileges: string[]) => void;
}
diff --git a/packages/ui/src/foundations/enterprise-module/providers/module.provider.tsx b/packages/ui/src/foundations/enterprise-module/providers/module.provider.tsx
index 3d405bb..e424703 100644
--- a/packages/ui/src/foundations/enterprise-module/providers/module.provider.tsx
+++ b/packages/ui/src/foundations/enterprise-module/providers/module.provider.tsx
@@ -82,6 +82,8 @@ export function EnterpriseModuleProvider<
const setMetaData = store((state: S) => state.setMetaData);
const filterData = store((state: S) => state.filterData);
const setFilterData = store((state: S) => state.setFilterData);
+ const tableConfig = store((state: S) => state.tableConfig);
+ const setTableConfig = store((state: S) => state.setTableConfig);
const selectionSlice = useMemo(
() => ({
@@ -91,8 +93,10 @@ export function EnterpriseModuleProvider<
setMetaData,
filterData,
setFilterData,
+ tableConfig,
+ setTableConfig,
}),
- [selectedRows, setSelectedRows, metaData, setMetaData, filterData, setFilterData],
+ [selectedRows, setSelectedRows, metaData, setMetaData, filterData, setFilterData, tableConfig, setTableConfig],
);
// ---------------------------------------------------------------------------
From 0f5a1ef1d13f373fc576f0804f6e5656160d2dfc Mon Sep 17 00:00:00 2001
From: Firman Ramdhani <33869609+firmanramdhani@users.noreply.github.com>
Date: Mon, 27 Jul 2026 17:45:57 +0700
Subject: [PATCH 3/3] feat: implement sidebar menu persistence using zustand
---
.../layouts/components/sidebar.store.ts | 92 +++++++++++
.../modules/layouts/components/sidebar.tsx | 153 +++++++++++++-----
apps/web/src/core/storage/local/index.ts | 7 +-
.../core-i18n/src/languages/en/common.json | 3 +-
.../core-i18n/src/languages/id/common.json | 3 +-
5 files changed, 212 insertions(+), 46 deletions(-)
create mode 100644 apps/web/src/apps/modules/layouts/components/sidebar.store.ts
diff --git a/apps/web/src/apps/modules/layouts/components/sidebar.store.ts b/apps/web/src/apps/modules/layouts/components/sidebar.store.ts
new file mode 100644
index 0000000..cc15116
--- /dev/null
+++ b/apps/web/src/apps/modules/layouts/components/sidebar.store.ts
@@ -0,0 +1,92 @@
+import { create } from 'zustand';
+import { appStorage, AppStorageKey } from '../../../../core/storage/local';
+
+interface SidebarState {
+ openedKeys: Set;
+ isInitialized: boolean;
+ isAllExpanded: boolean;
+ searchQuery: string;
+
+ // Actions
+ initializeStorage: (initialKeysFromActive: string[], allParentKeys: string[]) => Promise;
+ toggleMenu: (key: string, isOpened: boolean, allParentKeys: string[]) => Promise;
+ expandAll: (allParentKeys: string[]) => Promise;
+ collapseAll: () => Promise;
+ syncActiveKeys: (keys: string[], allParentKeys: string[]) => Promise;
+ setSearchQuery: (query: string) => void;
+}
+
+export const useSidebarStore = create((set, get) => ({
+ openedKeys: new Set(),
+ isInitialized: false,
+ isAllExpanded: false,
+ searchQuery: '',
+
+ initializeStorage: async (initialKeysFromActive, allParentKeys) => {
+ if (get().isInitialized) return;
+
+ const saved = await appStorage.getItem(AppStorageKey.SIDEBAR_OPEN_MENUS);
+
+ if (saved) {
+ const openedSet = new Set(saved);
+ const isAllExpanded = saved.length > 0 && allParentKeys.every((k) => openedSet.has(k));
+ set({ openedKeys: openedSet, isInitialized: true, isAllExpanded });
+ } else {
+ // First time load: use initial active keys
+ await appStorage.setItem(AppStorageKey.SIDEBAR_OPEN_MENUS, initialKeysFromActive);
+ const openedSet = new Set(initialKeysFromActive);
+ const isAllExpanded = initialKeysFromActive.length > 0 && allParentKeys.every((k) => openedSet.has(k));
+ set({ openedKeys: openedSet, isInitialized: true, isAllExpanded });
+ }
+ },
+
+ toggleMenu: async (key, isOpened, allParentKeys) => {
+ const prevSet = get().openedKeys;
+ const nextSet = new Set(prevSet);
+
+ if (isOpened) {
+ nextSet.add(key);
+ } else {
+ nextSet.delete(key);
+ }
+
+ const nextArr = Array.from(nextSet);
+ const isAllExpanded = nextArr.length > 0 && allParentKeys.every((k) => nextSet.has(k));
+
+ set({ openedKeys: nextSet, isAllExpanded });
+ await appStorage.setItem(AppStorageKey.SIDEBAR_OPEN_MENUS, nextArr);
+ },
+
+ expandAll: async (allParentKeys) => {
+ const nextSet = new Set(allParentKeys);
+ set({ openedKeys: nextSet, isAllExpanded: true });
+ await appStorage.setItem(AppStorageKey.SIDEBAR_OPEN_MENUS, allParentKeys);
+ },
+
+ collapseAll: async () => {
+ set({ openedKeys: new Set(), isAllExpanded: false });
+ await appStorage.setItem(AppStorageKey.SIDEBAR_OPEN_MENUS, []);
+ },
+
+ syncActiveKeys: async (keysToSync, allParentKeys) => {
+ const prevSet = get().openedKeys;
+ let hasChanges = false;
+ const nextSet = new Set(prevSet);
+
+ for (const key of keysToSync) {
+ if (!nextSet.has(key)) {
+ nextSet.add(key);
+ hasChanges = true;
+ }
+ }
+
+ if (hasChanges) {
+ const nextArr = Array.from(nextSet);
+ const isAllExpanded = nextArr.length > 0 && allParentKeys.every((k) => nextSet.has(k));
+ set({ openedKeys: nextSet, isAllExpanded });
+ await appStorage.setItem(AppStorageKey.SIDEBAR_OPEN_MENUS, nextArr);
+ }
+ },
+
+ setSearchQuery: (query: string) => set({ searchQuery: query }),
+}));
diff --git a/apps/web/src/apps/modules/layouts/components/sidebar.tsx b/apps/web/src/apps/modules/layouts/components/sidebar.tsx
index 3fb7189..9dcecb8 100644
--- a/apps/web/src/apps/modules/layouts/components/sidebar.tsx
+++ b/apps/web/src/apps/modules/layouts/components/sidebar.tsx
@@ -1,6 +1,18 @@
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Link, useLocation } from 'react-router-dom';
-import { Box, NavLink, Stack, Tooltip, ActionIcon, Divider, Menu, TextInput, Button, Group } from '@repo/ui/components';
+import {
+ Box,
+ NavLink,
+ Stack,
+ Tooltip,
+ ActionIcon,
+ Divider,
+ Menu,
+ TextInput,
+ Button,
+ Group,
+ Text,
+} from '@repo/ui/components';
import { useCoreAppShell } from '@repo/ui/components';
import { ChevronsLeft, ChevronsRight, PanelTopClose, PanelTopOpen, Search, X, History, Bookmark } from 'lucide-react';
import { publish } from '@repo/core-events';
@@ -9,6 +21,8 @@ import type { SidebarVariant } from '@repo/ui/components';
import { useTranslation } from '@repo/core-i18n';
import { shortcutsData } from '@repo/ui/constants';
import { LAYOUT_EVENTS } from '../../../../core/constants/events';
+import { useSidebarStore } from './sidebar.store';
+import { useDebouncedValue } from '@repo/ui/hooks';
// ---------------------------------------------------------------------------
// Props
@@ -39,17 +53,15 @@ interface SidebarMenuProps {
interface MenuItemExpandedProps {
item: MenuItemType;
activeKeys: Set;
- isSearching?: boolean;
- expandVersion?: number;
- collapseVersion?: number;
+ isSearching: boolean;
+ allParentKeys: string[];
}
const MenuItemExpanded = memo(function MenuItemExpanded({
item,
activeKeys,
isSearching,
- expandVersion = 0,
- collapseVersion = 0,
+ allParentKeys,
}: MenuItemExpandedProps) {
const { t } = useTranslation();
const Icon = item.icon;
@@ -59,26 +71,27 @@ const MenuItemExpanded = memo(function MenuItemExpanded({
const isExactActive = isActive && !hasChildren;
const isParentActive = isActive && hasChildren;
- const [opened, setOpened] = useState(isActive);
- const isOpened = isSearching || opened;
+ const isOpened = useSidebarStore((state) => state.openedKeys.has(item.key));
+ const toggleMenu = useSidebarStore((state) => state.toggleMenu);
- useEffect(() => {
- if (expandVersion > 0) setOpened(true);
- }, [expandVersion]);
+ const effectivelyOpened = isSearching || isOpened;
- useEffect(() => {
- if (collapseVersion > 0) setOpened(false);
- }, [collapseVersion]);
+ const handleChange = useCallback(
+ (newOpened: boolean) => {
+ toggleMenu(item.key, newOpened, allParentKeys);
+ },
+ [item.key, toggleMenu, allParentKeys],
+ );
return (
}
+ leftSection={}
active={isExactActive}
- opened={isOpened}
- onChange={setOpened}
+ opened={effectivelyOpened}
+ onChange={handleChange}
variant="light"
styles={{
root: {
@@ -89,6 +102,7 @@ const MenuItemExpanded = memo(function MenuItemExpanded({
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
+ fontWeight: 500,
},
}}
>
@@ -99,8 +113,7 @@ const MenuItemExpanded = memo(function MenuItemExpanded({
item={child}
activeKeys={activeKeys}
isSearching={isSearching}
- expandVersion={expandVersion}
- collapseVersion={collapseVersion}
+ allParentKeys={allParentKeys}
/>
))}
@@ -136,7 +149,7 @@ const MenuItemFlyout = memo(function MenuItemFlyout({ item, activeKeys, isRoot =
size="lg"
aria-label={t(item.label)}
>
-
+
);
@@ -153,6 +166,7 @@ const MenuItemFlyout = memo(function MenuItemFlyout({ item, activeKeys, isRoot =
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
maxWidth: 240,
+ fontWeight: 500,
},
}}
>
@@ -170,7 +184,7 @@ const MenuItemFlyout = memo(function MenuItemFlyout({ item, activeKeys, isRoot =
size="lg"
aria-label={t(item.label)}
>
-
+
) : (
{
+ let keys: string[] = [];
+ for (const item of items) {
+ if (item.children && item.children.length > 0) {
+ keys.push(item.key);
+ keys = keys.concat(getAllParentKeys(item.children));
+ }
+ }
+ return keys;
+};
+
export const SidebarMenu = memo(function SidebarMenu({
items,
variantOverride,
@@ -238,23 +263,27 @@ export const SidebarMenu = memo(function SidebarMenu({
const variant = variantOverride ?? contextVariant;
const isMini = variant === 'mini';
- const [searchQuery, setSearchQuery] = useState('');
+ const searchQuery = useSidebarStore((state) => state.searchQuery);
+ const setSearchQuery = useSidebarStore((state) => state.setSearchQuery);
+ const [debouncedSearchQuery] = useDebouncedValue(searchQuery, 200);
const inputRef = useRef(null);
- const isSearching = searchQuery.trim().length > 0;
+ const isSearching = debouncedSearchQuery.trim().length > 0;
- const [expandVersion, setExpandVersion] = useState(0);
- const [collapseVersion, setCollapseVersion] = useState(0);
- const [isAllExpanded, setIsAllExpanded] = useState(false);
+ const allParentKeys = useMemo(() => getAllParentKeys(items), [items]);
+ const isInitialized = useSidebarStore((state) => state.isInitialized);
+ const isAllExpanded = useSidebarStore((state) => state.isAllExpanded);
+ const initializeStorage = useSidebarStore((state) => state.initializeStorage);
+ const expandAll = useSidebarStore((state) => state.expandAll);
+ const collapseAll = useSidebarStore((state) => state.collapseAll);
+ const syncActiveKeys = useSidebarStore((state) => state.syncActiveKeys);
const handleToggleExpandAll = useCallback(() => {
if (isAllExpanded) {
- setCollapseVersion((v) => v + 1);
- setIsAllExpanded(false);
+ collapseAll();
} else {
- setExpandVersion((v) => v + 1);
- setIsAllExpanded(true);
+ expandAll(allParentKeys);
}
- }, [isAllExpanded]);
+ }, [isAllExpanded, collapseAll, expandAll, allParentKeys]);
// Recursive active state calculation
const activeKeys = useMemo(() => {
@@ -299,11 +328,33 @@ export const SidebarMenu = memo(function SidebarMenu({
return keys;
}, [pathname, items]);
+ // Sync initial state to local storage or auto-expand on navigation
+ useEffect(() => {
+ const parentKeysToSync: string[] = [];
+ const checkActiveParents = (item: MenuItemType) => {
+ if (item.children && item.children.length > 0) {
+ if (activeKeys.has(item.key)) {
+ parentKeysToSync.push(item.key);
+ }
+ item.children.forEach(checkActiveParents);
+ }
+ };
+ items.forEach(checkActiveParents);
+
+ if (!isInitialized) {
+ initializeStorage(parentKeysToSync, allParentKeys);
+ } else {
+ if (parentKeysToSync.length > 0) {
+ syncActiveKeys(parentKeysToSync, allParentKeys);
+ }
+ }
+ }, [activeKeys, items, isInitialized, initializeStorage, syncActiveKeys, allParentKeys]);
+
// Client-side recursive filtering logic
const filteredItems = useMemo(() => {
if (!isSearching) return items;
- const query = searchQuery.toLowerCase();
+ const query = debouncedSearchQuery.toLowerCase();
const filterItem = (item: MenuItemType): MenuItemType | null => {
const isMatch = t(item.label).toLowerCase().includes(query);
@@ -327,7 +378,7 @@ export const SidebarMenu = memo(function SidebarMenu({
};
return items.map(filterItem).filter((item): item is MenuItemType => item !== null);
- }, [items, isSearching, searchQuery]);
+ }, [items, isSearching, debouncedSearchQuery, t]);
const handleToggle = useCallback(() => {
setSidebarVariant(isMini ? 'expanded' : 'mini');
@@ -581,16 +632,32 @@ export const SidebarMenu = memo(function SidebarMenu({
)}
- {filteredItems.map((item) => (
-
- ))}
+ {filteredItems.length === 0 && isSearching ? (
+
+
+
+ {t('common:system_menu.notFound')}
+
+
+
+ ) : (
+ filteredItems.map((item) => (
+
+ ))
+ )}
{withToggle && (
diff --git a/apps/web/src/core/storage/local/index.ts b/apps/web/src/core/storage/local/index.ts
index d8e77b8..8e9b3ed 100644
--- a/apps/web/src/core/storage/local/index.ts
+++ b/apps/web/src/core/storage/local/index.ts
@@ -6,6 +6,7 @@ export const AppStorageKey = {
ACCESS_TOKEN: 'access_token',
REFRESH_TOKEN: 'refresh_token',
USER_ID: 'u_id',
+ SIDEBAR_OPEN_MENUS: 'sidebar_open_menus',
} as const;
export type AppStorageKeyValue = (typeof AppStorageKey)[keyof typeof AppStorageKey];
@@ -26,7 +27,11 @@ export const APP_STORAGE_ENCRYPTED_KEYS = new Set([
AppStorageKey.REFRESH_TOKEN,
]);
-export const APP_STORAGE_PLAIN_KEYS = new Set([AppStorageKey.LANGUAGE, AppStorageKey.THEME]);
+export const APP_STORAGE_PLAIN_KEYS = new Set([
+ AppStorageKey.LANGUAGE,
+ AppStorageKey.THEME,
+ AppStorageKey.SIDEBAR_OPEN_MENUS,
+]);
export const APP_DATABASE_ENCRYPTED_KEYS = new Set([]);
diff --git a/packages/core-i18n/src/languages/en/common.json b/packages/core-i18n/src/languages/en/common.json
index 148c95d..79f4bf1 100644
--- a/packages/core-i18n/src/languages/en/common.json
+++ b/packages/core-i18n/src/languages/en/common.json
@@ -154,7 +154,8 @@
},
"system_menu": {
"history": "History",
- "bookmark": "Bookmark"
+ "bookmark": "Bookmark",
+ "notFound": "Menu not found"
},
"fields": {
"id": "ID",
diff --git a/packages/core-i18n/src/languages/id/common.json b/packages/core-i18n/src/languages/id/common.json
index 6a5f8e4..d570b18 100644
--- a/packages/core-i18n/src/languages/id/common.json
+++ b/packages/core-i18n/src/languages/id/common.json
@@ -154,7 +154,8 @@
},
"system_menu": {
"history": "Riwayat",
- "bookmark": "Bookmark"
+ "bookmark": "Bookmark",
+ "notFound": "Menu tidak ditemukan"
},
"fields": {
"id": "ID",