185 lines
6.9 KiB
TypeScript
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>
|
|
);
|
|
}
|