365 lines
13 KiB
TypeScript
365 lines
13 KiB
TypeScript
import { ReactNode } from 'react';
|
|
import type { UseFormReturn } from 'react-hook-form';
|
|
import type { ZodType } from 'zod';
|
|
import type { BaseEntity, BaseRemoteDataServices } from '@repo/core-api/data-services';
|
|
import type { ModulePageHeaderProps } from '../components/module-page-header';
|
|
import { PageActionsProps } from '../../../components';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Module Constants & Base Types
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Standardized action constants across all enterprise modules.
|
|
* Used to define RBAC permissions, UI rendering logic, and event handling.
|
|
*/
|
|
export const ModuleAction = {
|
|
CREATE: 'CREATE',
|
|
EDIT: 'EDIT',
|
|
DELETE: 'DELETE',
|
|
DUPLICATE: 'DUPLICATE',
|
|
SAVE: 'SAVE',
|
|
|
|
PRINT: 'PRINT',
|
|
PRINT_COPY: 'PRINT_COPY',
|
|
|
|
APPROVAL: 'APPROVAL',
|
|
ACTIVATE: 'ACTIVATE',
|
|
DEACTIVATE: 'DEACTIVATE',
|
|
|
|
CONFIRM: 'CONFIRM',
|
|
CANCEL: 'CANCEL',
|
|
ROLLBACK: 'ROLLBACK',
|
|
HOLD: 'HOLD',
|
|
|
|
LOGS: 'LOGS',
|
|
NOTES: 'NOTES',
|
|
|
|
FILTER: 'FILTER',
|
|
CONFIG: 'CONFIG',
|
|
} as const;
|
|
|
|
/**
|
|
* Represents a valid module action.
|
|
* Utilizes the `(string & {})` pattern to preserve IDE autocomplete for standard actions
|
|
* while allowing implementers to extend it with custom string identifiers.
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
export type ModuleActionType = (typeof ModuleAction)[keyof typeof ModuleAction] | (string & {});
|
|
|
|
/**
|
|
* Defines the architectural category of the module (e.g., routed vs. modal-driven).
|
|
*/
|
|
export type ModuleCategoryType = 'SINGLE_PAGE' | 'FULL_PAGE';
|
|
type ModuleType = 'TRANSACTION' | 'MASTER_DATA';
|
|
|
|
/**
|
|
* Represents the current operational mode of a form instance.
|
|
*/
|
|
export type FormPageType = 'CREATE' | 'EDIT' | 'DUPLICATE';
|
|
|
|
export interface SinglePageConfig {
|
|
type?: 'MODAL' | 'DRAWER';
|
|
size?: string | number;
|
|
}
|
|
|
|
export interface SinglePageModalState {
|
|
open: boolean;
|
|
dataId?: string;
|
|
}
|
|
|
|
export interface SinglePageFormState extends SinglePageModalState {
|
|
formType: FormPageType;
|
|
}
|
|
|
|
export interface DraftConfig {
|
|
enableDraft?: boolean;
|
|
/** @default 5000 */
|
|
autoSaveIntervalMs?: number;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Root Provider Configuration
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Foundational configuration injected into the root ModuleProvider.
|
|
* @template E The base database entity for the module.
|
|
* @property moduleKey Unique identifier for the module, used for permissions, caching, and i18n.
|
|
* @property webUrl Base URL for the module's web routes.
|
|
* @property apiUrl Base API endpoint for the module's remote data services.
|
|
* @property tabTitle Optional title for browser tabs. When omitted, the Provider will auto-set it from the module's translation key `title`.
|
|
* @property moduleCategory Architectural category of the module, used for rendering and routing logic.
|
|
* @property translationNamespace Translation namespace for i18n resource bundles. Must match the namespace string used in `registerModuleNamespace()`.
|
|
* @property singlePageFormConfig Optional configuration for single-page form modals or drawers.
|
|
* @property singlePageDetailConfig Optional configuration for single-page detail modals or drawers.
|
|
*/
|
|
export interface ModuleConfigEntity<E extends BaseEntity = BaseEntity> {
|
|
_data?: E; // Fix unused generic
|
|
moduleKey: string;
|
|
webUrl: string;
|
|
apiUrl: string;
|
|
tabTitle?: string;
|
|
moduleCategory: ModuleCategoryType;
|
|
moduleType: ModuleType;
|
|
translationNamespace: string;
|
|
|
|
singlePageFormConfig?: SinglePageConfig;
|
|
singlePageDetailConfig?: SinglePageConfig;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Context Slices (Anti-Rerender Strategy)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface ConfigSlice<E extends BaseEntity = BaseEntity> {
|
|
config: ModuleConfigEntity<E>;
|
|
privileges: PrivilegeEntity;
|
|
IS_MACOS: boolean;
|
|
}
|
|
|
|
export interface DataServiceSlice<
|
|
E extends BaseEntity,
|
|
S extends BaseRemoteDataServices<E> = BaseRemoteDataServices<E>,
|
|
> {
|
|
dataServices: S;
|
|
}
|
|
|
|
/**
|
|
* State management slice for grid/table selections and data filtering.
|
|
* Utilizes generic parameters to ensure type safety for arbitrary filter and metadata payloads.
|
|
*/
|
|
export interface SelectionSlice<
|
|
E extends BaseEntity = BaseEntity,
|
|
TFilter = Record<string, unknown>,
|
|
TMeta = Record<string, unknown>,
|
|
> {
|
|
selectedRows: E[];
|
|
setSelectedRows: (rows: E[]) => 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 {
|
|
navigateToIndex: () => void;
|
|
navigateToCreate: () => void;
|
|
navigateToEdit: (id: string) => void;
|
|
navigateToDetail: (id: string) => void;
|
|
navigateToDuplicate: (id: string) => void;
|
|
}
|
|
|
|
export interface ModalSlice {
|
|
formState: SinglePageFormState;
|
|
setFormState: (state: SinglePageFormState) => void;
|
|
detailState: SinglePageModalState;
|
|
setDetailState: (state: SinglePageModalState) => void;
|
|
}
|
|
|
|
/**
|
|
* Translation slice for enterprise modules, providing a standardized interface for i18n operations.
|
|
*
|
|
* The `t` function is scoped to `[moduleNamespace, 'common']`, meaning:
|
|
* - Keys are first resolved in the module's own namespace.
|
|
* - If not found, they fall back to the `common` namespace.
|
|
* - Explicit namespace prefix (e.g. `common:save`) is still supported.
|
|
*
|
|
* @property t - Translation function scoped to the module's namespace with common fallback.
|
|
*/
|
|
export interface TranslationSlice {
|
|
/** Translation function scoped to [moduleNamespace, 'common']. No prefix needed for module keys. */
|
|
t: (key: string, options?: Record<string, unknown>) => string;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Page-Level Configurations
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Lifecycle interceptors for form processing.
|
|
* @template E The base database entity.
|
|
* @template TFormData The payload structure (defaults to Partial<E> for DTOs).
|
|
*/
|
|
export interface EnterpriseFormLifecycleHooks<E extends BaseEntity, TFormData = Partial<E>> {
|
|
onValidate?: (data: TFormData) => Promise<boolean>;
|
|
beforeSave?: (data: TFormData) => Promise<TFormData>;
|
|
/** Overrides the default repository save implementation. */
|
|
save?: (data: TFormData) => Promise<E>;
|
|
afterSave?: (result: E) => Promise<void>;
|
|
afterGetData?: (data: E) => Promise<TFormData>;
|
|
}
|
|
|
|
interface BasePageConfig {
|
|
children?: ReactNode;
|
|
px?: string | number;
|
|
py?: string | number;
|
|
pageHeaderProps?: Omit<ModulePageHeaderProps, 'actions' | 'moduleKey'>;
|
|
}
|
|
export interface EnterpriseIndexPageConfig extends BasePageConfig {
|
|
customPageActions?: (actions: PageActionsProps['actions']) => PageActionsProps['actions'];
|
|
onClickCreate?: (key: string) => void;
|
|
}
|
|
|
|
export interface EnterpriseFormPageConfig<E extends BaseEntity = BaseEntity> extends EnterpriseFormLifecycleHooks<E> {
|
|
children?: ReactNode;
|
|
showPageHeader?: boolean;
|
|
useDefaultPadding?: boolean;
|
|
customHiddenActions?: (data: Partial<E>, defaultHidden: string[]) => string[];
|
|
/** Strongly typed event handler for custom form interactions. */
|
|
onCustomActionClick?: (key: ModuleActionType, data?: unknown) => void;
|
|
|
|
draftConfig?: DraftConfig;
|
|
/** Type-safe array of entity keys to exclude during an update operation. */
|
|
ignoreKeyUpdate?: (keyof E)[];
|
|
/** Type-safe array of entity keys to exclude when duplicating a record. */
|
|
ignoreKeyDuplicate?: (keyof E)[];
|
|
initialValue?: Partial<E>;
|
|
|
|
presetDuplicate?: (data: E) => Promise<Partial<E>>;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Action Confirmation Modal Configuration
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Configuration for the confirmation modal shown before executing a lifecycle action.
|
|
*
|
|
* Supports three modes:
|
|
* 1. **Simple confirmation** — no `renderBody`, just a confirm/cancel dialog.
|
|
* 2. **Static body** — `renderBody` returns static JSX (text, checkboxes, etc.).
|
|
* 3. **Form body** — `renderBody` receives an RHF `UseFormReturn` instance.
|
|
* The implementer renders `Field*` components bound to the form.
|
|
* Form data is validated via `schema` before submission and sent as `meta`.
|
|
*
|
|
* @template TMeta The shape of the form data (defaults to Record<string, unknown>).
|
|
*/
|
|
export interface ActionModalConfig<TMeta extends Record<string, unknown> = Record<string, unknown>> {
|
|
/** Modal title override. Defaults to action-specific translation (e.g., "Delete Item?") */
|
|
title?: string;
|
|
/**
|
|
* Custom modal body renderer.
|
|
*
|
|
* When provided WITHOUT `schema`/`defaultValues`, receives `undefined` — render static content.
|
|
* When provided WITH `schema`/`defaultValues`, receives a fully-typed `UseFormReturn<TMeta>`
|
|
* instance. Use `Field*` components from `@repo/ui/form` bound to this form.
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* // Static body (no form)
|
|
* renderBody: () => <Text>Are you sure you want to delete this item?</Text>
|
|
*
|
|
* // Form body
|
|
* renderBody: (form) => (
|
|
* <FieldTextarea name="reason" control={form.control} label="Reason" />
|
|
* )
|
|
* ```
|
|
*/
|
|
renderBody?: (form?: UseFormReturn<TMeta>) => ReactNode;
|
|
/** Zod schema for validating the form body. When omitted, no validation is applied */
|
|
schema?: ZodType<TMeta>;
|
|
/** Default values for the form. Required when schema is provided */
|
|
defaultValues?: TMeta;
|
|
/** Confirm button label override. Defaults to action translation */
|
|
confirmLabel?: string;
|
|
/** Cancel button label override. Defaults to t('common:actions.cancel') */
|
|
cancelLabel?: string;
|
|
/** Size of the modal. @default 'md' */
|
|
size?: string | number;
|
|
/** Custom success message or a function to generate it after action completes successfully */
|
|
successMessage?: string | ((data?: any) => string);
|
|
/** Custom error message or a function to extract it from the network error */
|
|
errorMessage?: string | ((error: any) => string);
|
|
}
|
|
|
|
/**
|
|
* Internal state for the action confirmation modal managed by the detail page provider.
|
|
* @internal
|
|
*/
|
|
export interface ActionModalState<E extends BaseEntity = BaseEntity> {
|
|
opened: boolean;
|
|
action: ModuleActionType | null;
|
|
data: E | null;
|
|
config?: ActionModalConfig<any>;
|
|
}
|
|
|
|
export interface EnterpriseDetailPageConfig<E extends BaseEntity = BaseEntity> extends BasePageConfig {
|
|
editMode?: 'FULL' | 'PARTIAL';
|
|
customPageActions?: (data: E, actions: PageActionsProps['actions']) => PageActionsProps['actions'];
|
|
onDetailLoaded?: (data: E) => void;
|
|
|
|
showHighlightData?: boolean;
|
|
showHighlightDataOnBreadcrumbs?: boolean;
|
|
highlightDataKey?: string;
|
|
|
|
/** Key to reference status data in the entity, used to render the status badge automatically. @default 'status' */
|
|
statusKey?: string;
|
|
/** Custom callback to provide dynamic status badge properties */
|
|
getCustomStatusBadgeConfig?: (status: string) => Partial<import('@mantine/core').BadgeProps>;
|
|
|
|
onClickCreate?: () => void;
|
|
onClickDuplicate?: (data: E) => void;
|
|
onClickEdit?: (data: E) => void;
|
|
onClickDelete?: (data: E) => void;
|
|
|
|
// Master Data Feature
|
|
onClickActivate?: (data: E) => void;
|
|
onClickDeactivate?: (data: E) => void;
|
|
|
|
// Transaction Feature
|
|
onClickConfirm?: (data: E) => void;
|
|
onClickCancel?: (data: E) => void;
|
|
onClickRollback?: (data: E) => void;
|
|
onClickHold?: (data: E) => void;
|
|
|
|
// Action Modal Configurations
|
|
deleteModalConfig?: ActionModalConfig;
|
|
activateModalConfig?: ActionModalConfig;
|
|
deactivateModalConfig?: ActionModalConfig;
|
|
confirmModalConfig?: ActionModalConfig;
|
|
cancelModalConfig?: ActionModalConfig;
|
|
rollbackModalConfig?: ActionModalConfig;
|
|
holdModalConfig?: ActionModalConfig;
|
|
}
|
|
|
|
export interface PrivilegeEntity {
|
|
ALLOW_VIEW: boolean;
|
|
ALLOW_CREATE: boolean;
|
|
ALLOW_EDIT: boolean;
|
|
ALLOW_DELETE: boolean;
|
|
|
|
ALLOW_PRINT: boolean;
|
|
ALLOW_PRINT_COPY: boolean;
|
|
|
|
ALLOW_APPROVAL: boolean;
|
|
ALLOW_ACTIVATE: boolean;
|
|
ALLOW_DEACTIVATE: boolean;
|
|
|
|
ALLOW_CONFIRM: boolean;
|
|
ALLOW_CANCEL: boolean;
|
|
ALLOW_ROLLBACK: boolean;
|
|
ALLOW_HOLD: boolean;
|
|
|
|
ALLOW_LOGS: boolean;
|
|
ALLOW_NOTES: boolean;
|
|
}
|