refactor: integrate Zustand store into EnterpriseModuleProvider and update module configurations to use store-based state management

This commit is contained in:
Firman Ramdhani
2026-07-20 14:09:46 +07:00
parent d089c40707
commit dd18787ee7
11 changed files with 116 additions and 37 deletions
@@ -120,11 +120,6 @@ export interface DataServiceSlice<
S extends BaseRemoteDataServices<E> = BaseRemoteDataServices<E>,
> {
dataServices: S;
/**
* User privileges payload for the current module.
* Typed as `unknown` to enforce strict type checking before consumption via RBAC utilities.
*/
privilege: unknown;
}
/**
@@ -138,10 +133,28 @@ export interface SelectionSlice<
> {
selectedRows: E[];
setSelectedRows: (rows: E[]) => void;
metaData: TMeta;
setMetaData: (data: TMeta) => void;
filterData: TFilter;
setFilterData: (data: TFilter) => void;
metaData: TMeta | null;
setMetaData: (data: TMeta | null) => void;
filterData: TFilter | null;
setFilterData: (data: TFilter | null) => void;
}
/**
* Base generic state for Enterprise Module store using Zustand.
*/
export interface EnterpriseModuleState<
E extends BaseEntity = BaseEntity,
TFilter = Record<string, unknown>,
TMeta = Record<string, unknown>
> {
metaData: TMeta | null;
setMetaData: (data: TMeta | null) => void;
filterData: TFilter | null;
setFilterData: (data: TFilter | null) => void;
selectedRows: E[];
setSelectedRows: (rows: E[]) => void;
privileges: string[];
setPrivileges: (privileges: string[]) => void;
}
export interface NavigationSlice {
@@ -1,9 +1,9 @@
import { useMemo, useState, ReactNode } from 'react';
import type { UseBoundStore, StoreApi } from 'zustand';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from '@repo/core-i18n';
import { BaseEntity, BaseRemoteDataServices } from '@repo/core-api/data-services';
import { ConfigSlice, ModuleConfigEntity, SinglePageFormState, SinglePageModalState } from '../entities/entity';
import { ConfigSlice, ModuleConfigEntity, SinglePageFormState, SinglePageModalState, EnterpriseModuleState, PrivilegeEntity } from '../entities/entity';
import {
EnterpriseConfigContext,
EnterpriseDataServiceContext,
@@ -15,30 +15,42 @@ import {
import { defaultPrivileges } from '../constant/default-privilege';
import { Forbidden } from '../../../components';
export interface EnterpriseModuleProviderProps<E extends BaseEntity> {
export interface EnterpriseModuleProviderProps<
E extends BaseEntity,
S extends EnterpriseModuleState<E> = EnterpriseModuleState<E>
> {
children: ReactNode;
config: ModuleConfigEntity<E>;
dataServices: BaseRemoteDataServices<E>;
store: UseBoundStore<StoreApi<S>>;
}
/** Detect macOS / iOS for displaying platform-specific shortcut labels. */
const IS_MACOS = typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.userAgent);
export function EnterpriseModuleProvider<E extends BaseEntity>(props: EnterpriseModuleProviderProps<E>) {
const { children, config, dataServices } = props;
export function EnterpriseModuleProvider<
E extends BaseEntity,
S extends EnterpriseModuleState<E> = EnterpriseModuleState<E>
>(props: EnterpriseModuleProviderProps<E, S>) {
const { children, config, dataServices, store } = props;
const navigate = useNavigate();
// ---------------------------------------------------------------------------
// 1. Config Slice (Static)
// ---------------------------------------------------------------------------
const storePrivileges = store((state: S) => state.privileges);
const configSlice: ConfigSlice = useMemo(() => {
// FIXME => Parsing privilege: mapping string array dari store ke format PrivilegeEntity (boolean)
const parsedPrivileges = defaultPrivileges
return {
config,
// FIXME => IMPLEMENT PRIVILEGE
privileges: defaultPrivileges,
privileges: parsedPrivileges,
IS_MACOS: IS_MACOS,
};
}, [config]);
}, [config, storePrivileges]);
// ---------------------------------------------------------------------------
// 1b. Translation Slice (Dedicated context — decoupled from config)
@@ -59,15 +71,18 @@ export function EnterpriseModuleProvider<E extends BaseEntity>(props: Enterprise
// 2. Data Service Slice (Stable refs)
// ---------------------------------------------------------------------------
const dataServiceSlice = useMemo(() => {
return { dataServices, privilege: undefined };
return { dataServices };
}, [dataServices]);
// ---------------------------------------------------------------------------
// 3. Selection Slice (Dynamic state)
// ---------------------------------------------------------------------------
const [selectedRows, setSelectedRows] = useState<E[]>([]);
const [metaData, setMetaData] = useState<any>(null);
const [filterData, setFilterData] = useState<any>(null);
const selectedRows = store((state: S) => state.selectedRows);
const setSelectedRows = store((state: S) => state.setSelectedRows);
const metaData = store((state: S) => state.metaData);
const setMetaData = store((state: S) => state.setMetaData);
const filterData = store((state: S) => state.filterData);
const setFilterData = store((state: S) => state.setFilterData);
const selectionSlice = useMemo(
() => ({
@@ -78,7 +93,7 @@ export function EnterpriseModuleProvider<E extends BaseEntity>(props: Enterprise
filterData,
setFilterData,
}),
[selectedRows, metaData, filterData],
[selectedRows, setSelectedRows, metaData, setMetaData, filterData, setFilterData],
);
// ---------------------------------------------------------------------------