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, EnterpriseModuleState, PrivilegeEntity, } from '../entities/entity'; import { EnterpriseConfigContext, EnterpriseDataServiceContext, EnterpriseSelectionContext, EnterpriseNavigationContext, EnterpriseModalContext, EnterpriseTranslationContext, } from '../hooks/use-module.context'; import { defaultPrivileges } from '../constant/default-privilege'; import { Forbidden } from '../../../components'; export interface EnterpriseModuleProviderProps< E extends BaseEntity, S extends EnterpriseModuleState = EnterpriseModuleState, > { children: ReactNode; config: ModuleConfigEntity; dataServices: BaseRemoteDataServices; store: UseBoundStore>; } /** 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, S extends EnterpriseModuleState = EnterpriseModuleState, >(props: EnterpriseModuleProviderProps) { const { children, config, dataServices, store } = props; const navigate = useNavigate(); // --------------------------------------------------------------------------- // 1. Config Slice (Static) // --------------------------------------------------------------------------- const storePrivileges = store((state: S) => state.privileges); const configSlice: ConfigSlice = useMemo(() => { // Parsing privilege: mapping string array from store to PrivilegeEntity format (boolean) const parsedPrivileges: PrivilegeEntity = defaultPrivileges; return { config, privileges: parsedPrivileges, IS_MACOS: IS_MACOS, }; }, [config, storePrivileges]); // --------------------------------------------------------------------------- // 1b. Translation Slice (Dedicated context — decoupled from config) // --------------------------------------------------------------------------- const namespaces = useMemo(() => [config.translationNamespace, 'common'], [config.translationNamespace]); const { t } = useTranslation(namespaces); const translationSlice = useMemo(() => ({ t: t as (key: string, options?: Record) => string }), [t]); // Set document title — uses explicit tabTitle or falls back to translation key 'title' // useEffect(() => { // const title = config.tabTitle || t('title'); // if (title) { // document.title = title; // } // }, [config.tabTitle, t]); // --------------------------------------------------------------------------- // 2. Data Service Slice (Stable refs) // --------------------------------------------------------------------------- const dataServiceSlice = useMemo(() => { return { dataServices }; }, [dataServices]); // --------------------------------------------------------------------------- // 3. Selection Slice (Dynamic state) // --------------------------------------------------------------------------- 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( () => ({ selectedRows, setSelectedRows, metaData, setMetaData, filterData, setFilterData, }), [selectedRows, setSelectedRows, metaData, setMetaData, filterData, setFilterData], ); // --------------------------------------------------------------------------- // 4. Modal Slice (For Single Page mode) // --------------------------------------------------------------------------- const [formState, setFormState] = useState({ open: false, formType: 'CREATE' }); const [detailState, setDetailState] = useState({ open: false }); const modalSlice = useMemo( () => ({ formState, setFormState, detailState, setDetailState }), [formState, detailState], ); // --------------------------------------------------------------------------- // 5. Navigation Slice // --------------------------------------------------------------------------- const navigationSlice = useMemo(() => { const isSingle = config.moduleCategory === 'SINGLE_PAGE'; return { navigateToIndex: () => { if (isSingle) { setFormState({ open: false, formType: 'CREATE' }); setDetailState({ open: false }); } else { navigate(`${config.webUrl}/index`); } }, navigateToCreate: () => { if (isSingle) { setFormState({ open: true, formType: 'DUPLICATE' }); } else { navigate(`${config.webUrl}/create`); } }, navigateToEdit: (id: string) => { if (isSingle) { setFormState({ open: true, formType: 'EDIT', dataId: id }); } else { navigate(`${config.webUrl}/edit/${id}`); } }, navigateToDetail: (id: string) => { if (isSingle) { setDetailState({ open: true, dataId: id }); } else { navigate(`${config.webUrl}/detail/${id}`); } }, navigateToDuplicate: (id: string) => { if (isSingle) { setFormState({ open: true, formType: 'DUPLICATE', dataId: id }); } else { navigate(`${config.webUrl}/duplicate/${id}`); } }, }; }, [config.moduleCategory, config.webUrl, navigate]); // --------------------------------------------------------------------------- // Render // --------------------------------------------------------------------------- const { ALLOW_VIEW } = configSlice.privileges; if (!ALLOW_VIEW) return ; return ( {children} ); }