feat: add moduleType to configurations, introduce DetailPageProvider, and consolidate macOS detection into module provider context

This commit is contained in:
Firman Ramdhani
2026-07-13 15:39:10 +07:00
parent 0bbd376125
commit 383e2627e0
7 changed files with 41 additions and 41 deletions
@@ -19,4 +19,7 @@ export const FullPageModuleConfig: ModuleConfigEntity = {
/** Architectural category of the module, used for rendering and routing logic */ /** Architectural category of the module, used for rendering and routing logic */
moduleCategory: 'FULL_PAGE', moduleCategory: 'FULL_PAGE',
/** */
moduleType: 'MASTER_DATA',
} as const; } as const;
@@ -19,4 +19,7 @@ export const SinglePageModuleConfig: ModuleConfigEntity = {
/** Architectural category of the module, used for rendering and routing logic */ /** Architectural category of the module, used for rendering and routing logic */
moduleCategory: 'SINGLE_PAGE', moduleCategory: 'SINGLE_PAGE',
/** */
moduleType: 'MASTER_DATA',
} as const; } as const;
@@ -46,6 +46,7 @@ export type ModuleActionType = (typeof ModuleAction)[keyof typeof ModuleAction]
* Defines the architectural category of the module (e.g., routed vs. modal-driven). * Defines the architectural category of the module (e.g., routed vs. modal-driven).
*/ */
export type ModuleCategoryType = 'SINGLE_PAGE' | 'FULL_PAGE'; export type ModuleCategoryType = 'SINGLE_PAGE' | 'FULL_PAGE';
type ModuleType = 'TRANSACTION' | 'MASTER_DATA';
/** /**
* Represents the current operational mode of a form instance. * Represents the current operational mode of a form instance.
@@ -95,6 +96,7 @@ export interface ModuleConfigEntity<E extends BaseEntity = BaseEntity> {
apiUrl: string; apiUrl: string;
tabTitle?: string; tabTitle?: string;
moduleCategory: ModuleCategoryType; moduleCategory: ModuleCategoryType;
moduleType: ModuleType;
translationNamespace: string; translationNamespace: string;
singlePageFormConfig?: SinglePageConfig; singlePageFormConfig?: SinglePageConfig;
@@ -108,6 +110,7 @@ export interface ModuleConfigEntity<E extends BaseEntity = BaseEntity> {
export interface ConfigSlice<E extends BaseEntity = BaseEntity> { export interface ConfigSlice<E extends BaseEntity = BaseEntity> {
config: ModuleConfigEntity<E>; config: ModuleConfigEntity<E>;
privileges: PrivilegeEntity; privileges: PrivilegeEntity;
IS_MACOS: boolean;
} }
export interface DataServiceSlice< export interface DataServiceSlice<
@@ -187,20 +190,12 @@ export interface EnterpriseFormLifecycleHooks<E extends BaseEntity, TFormData =
afterGetData?: (data: E) => Promise<TFormData>; afterGetData?: (data: E) => Promise<TFormData>;
} }
export interface EnterpriseIndexPageConfig<E extends BaseEntity = BaseEntity> { export interface EnterpriseIndexPageConfig {
_data?: E; // Fix unused generic
children?: ReactNode; children?: ReactNode;
px?: string | number; px?: string | number;
py?: string | number; py?: string | number;
// customHiddenActions?: (selected: E[], defaultHidden: string[]) => string[];
// /** Strongly typed event handler to prevent arbitrary string usage for actions. */
// onCustomActionClick?: (key: ModuleActionType, data?: unknown) => void;
// filterDrawerContent?: ReactNode;
// registerRefreshCallback?: (callback: () => void) => void;
pageHeaderProps?: Omit<ModulePageHeaderProps, 'actions' | 'moduleKey'>; pageHeaderProps?: Omit<ModulePageHeaderProps, 'actions' | 'moduleKey'>;
// actions?: PageActionsProps['actions'];
customPageActions?: (actions: PageActionsProps['actions']) => PageActionsProps['actions']; customPageActions?: (actions: PageActionsProps['actions']) => PageActionsProps['actions'];
onClickCreate?: (key: string) => void; onClickCreate?: (key: string) => void;
} }
@@ -227,11 +222,10 @@ export interface EnterpriseDetailPageConfig<E extends BaseEntity = BaseEntity> {
children?: ReactNode; children?: ReactNode;
showPageHeader?: boolean; showPageHeader?: boolean;
useDefaultPadding?: boolean; useDefaultPadding?: boolean;
customHiddenActions?: (data: E | null, defaultHidden: string[]) => string[];
onCustomActionClick?: (key: ModuleActionType, data?: unknown) => void;
editMode?: 'FULL' | 'PARTIAL'; editMode?: 'FULL' | 'PARTIAL';
afterGetData?: (data: E) => Promise<unknown>;
pageHeaderProps?: Omit<ModulePageHeaderProps, 'actions' | 'moduleKey'>;
customPageActions?: (data: E, actions: PageActionsProps['actions']) => PageActionsProps['actions'];
} }
export interface PrivilegeEntity { export interface PrivilegeEntity {
@@ -1,20 +1,10 @@
import { createContext, useContext } from 'react'; import { createContext, useContext } from 'react';
import type { BaseEntity } from '@repo/core-api/data-services';
export interface IndexPageContextValue<E extends BaseEntity = BaseEntity> { export interface IndexPageContextValue {}
_data?: E; // Fix unused generic
// renderRowActions: (row: E) => React.ReactNode;
// refreshGrid: () => void;
// // State for batch modals
// isConfirmModalOpen: boolean;
// setIsConfirmModalOpen: (open: boolean) => void;
// isDeleteModalOpen: boolean;
// setIsDeleteModalOpen: (open: boolean) => void;
}
export const IndexPageContext = createContext<IndexPageContextValue<any> | null>(null); export const IndexPageContext = createContext<IndexPageContextValue | null>(null);
export function useIndexPageContext<E extends BaseEntity = BaseEntity>(): IndexPageContextValue<E> { export function useIndexPageContext(): IndexPageContextValue {
const context = useContext(IndexPageContext); const context = useContext(IndexPageContext);
if (!context) { if (!context) {
throw new Error('useIndexPageContext must be used within an IndexPageProvider'); throw new Error('useIndexPageContext must be used within an IndexPageProvider');
@@ -0,0 +1,17 @@
import { BaseEntity } from '@repo/core-api/data-services';
import { DetailPageContext } from '../hooks/use-detail-page.context';
import { EnterpriseDetailPageConfig } from '../entities/entity';
export function EnterpriseDetailPageProvider<E extends BaseEntity = BaseEntity>(props: EnterpriseDetailPageConfig<E>) {
const {
children,
showPageHeader,
useDefaultPadding,
editMode,
pageHeaderProps,
customPageActions,
} = props;
return <DetailPageContext.Provider value={{}}></DetailPageContext.Provider>;
}
@@ -12,23 +12,12 @@ import {
} from '../hooks/use-module.context'; } from '../hooks/use-module.context';
import { shortcutsData } from '../../../constants'; import { shortcutsData } from '../../../constants';
// --------------------------------------------------------------------------- export function EnterpriseIndexPageProvider(props: EnterpriseIndexPageConfig) {
// Helpers
// ---------------------------------------------------------------------------
/** Detect macOS / iOS for displaying platform-specific shortcut labels. */
const IS_MAC = typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.userAgent);
// ---------------------------------------------------------------------------
// Component
// ---------------------------------------------------------------------------
export function EnterpriseIndexPageProvider<E extends BaseEntity = BaseEntity>(props: EnterpriseIndexPageConfig<E>) {
const { children, pageHeaderProps, px, py, customPageActions, onClickCreate } = props; const { children, pageHeaderProps, px, py, customPageActions, onClickCreate } = props;
const { t } = useEnterpriseModuleTranslationContext(); const { t } = useEnterpriseModuleTranslationContext();
const navigation = useEnterpriseModuleNavigationContext(); const navigation = useEnterpriseModuleNavigationContext();
const { config, privileges } = useEnterpriseModuleConfigContext(); const { config, privileges, IS_MACOS } = useEnterpriseModuleConfigContext();
const { moduleKey } = config; const { moduleKey } = config;
const { ALLOW_CREATE } = privileges; const { ALLOW_CREATE } = privileges;
@@ -50,8 +39,8 @@ export function EnterpriseIndexPageProvider<E extends BaseEntity = BaseEntity>(p
/** Platform-aware shortcut label for the Create action. */ /** Platform-aware shortcut label for the Create action. */
const CREATE_SHORTCUT_LABEL = useMemo(() => { const CREATE_SHORTCUT_LABEL = useMemo(() => {
const shortcutData = shortcutsData.find((s) => s.key === 'collapse_sidebar'); const shortcutData = shortcutsData.find((s) => s.key === 'collapse_sidebar');
return IS_MAC ? shortcutData?.macKeyIcons.join(' ') : shortcutData?.winKeyIcons.join(' '); return IS_MACOS ? shortcutData?.macKeyIcons.join(' ') : shortcutData?.winKeyIcons.join(' ');
}, [IS_MAC]); }, [IS_MACOS]);
useEffect(() => { useEffect(() => {
function onKeyDown(e: KeyboardEvent) { function onKeyDown(e: KeyboardEvent) {
@@ -20,6 +20,9 @@ export interface EnterpriseModuleProviderProps<E extends BaseEntity> {
dataServices: BaseRemoteDataServices<E>; dataServices: BaseRemoteDataServices<E>;
} }
/** 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>) { export function EnterpriseModuleProvider<E extends BaseEntity>(props: EnterpriseModuleProviderProps<E>) {
const { children, config, dataServices } = props; const { children, config, dataServices } = props;
const navigate = useNavigate(); const navigate = useNavigate();
@@ -32,6 +35,7 @@ export function EnterpriseModuleProvider<E extends BaseEntity>(props: Enterprise
config, config,
// FIXME => IMPLEMENT PRIVILEGE // FIXME => IMPLEMENT PRIVILEGE
privileges: defaultPrivileges, privileges: defaultPrivileges,
IS_MACOS: IS_MACOS,
}; };
}, [config]); }, [config]);