Files
trackgo-fe/packages/ui/src/foundations/enterprise-module/providers/module.provider.tsx
T

185 lines
6.9 KiB
TypeScript

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<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,
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(() => {
// 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, unknown>) => 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<SinglePageFormState>({ open: false, formType: 'CREATE' });
const [detailState, setDetailState] = useState<SinglePageModalState>({ 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 <Forbidden homeUrl="/app" height={500} />;
return (
<EnterpriseConfigContext.Provider value={configSlice}>
<EnterpriseTranslationContext.Provider value={translationSlice}>
<EnterpriseDataServiceContext.Provider value={dataServiceSlice}>
<EnterpriseNavigationContext.Provider value={navigationSlice}>
<EnterpriseSelectionContext.Provider value={selectionSlice}>
<EnterpriseModalContext.Provider value={modalSlice}>{children}</EnterpriseModalContext.Provider>
</EnterpriseSelectionContext.Provider>
</EnterpriseNavigationContext.Provider>
</EnterpriseDataServiceContext.Provider>
</EnterpriseTranslationContext.Provider>
</EnterpriseConfigContext.Provider>
);
}