feat: add RowActions component for enhanced row-level actions in data grids

- Introduced RowActions component to manage row-level actions with tooltips and dropdown menus.
- Created types for row actions and page actions to standardize action properties.
- Implemented utility function to map action intents to Mantine theme colors.
- Updated CoreAppShell component to support optional slots for better flexibility.
- Added enterprise module structure with context hooks for managing module state and actions.
- Implemented draft management for forms to enhance user experience during data entry.
- Established context providers for detail, form, and index pages to streamline data handling.
- Updated dependencies to ensure compatibility with the latest versions.
This commit is contained in:
Firman Ramdhani
2026-07-01 17:04:20 +07:00
parent 1c2090f4fb
commit 8f14c4bc7b
60 changed files with 2188 additions and 442 deletions
@@ -0,0 +1,225 @@
import { ReactNode } from 'react';
import type { BaseEntity, BaseRemoteDataServices } from '@repo/core-api/data-services';
// ---------------------------------------------------------------------------
// 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',
} 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';
/**
* 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> {
moduleKey: string;
webUrl: string;
apiUrl: string;
tabTitle?: string;
moduleCategory: ModuleCategoryType;
translationNamespace: string;
singlePageFormConfig?: SinglePageConfig;
singlePageDetailConfig?: SinglePageConfig;
}
// ---------------------------------------------------------------------------
// Context Slices (Anti-Rerender Strategy)
// ---------------------------------------------------------------------------
export interface ConfigSlice<E extends BaseEntity = BaseEntity> {
config: ModuleConfigEntity<E>;
}
export interface DataServiceSlice<
E extends BaseEntity,
S extends BaseRemoteDataServices<E> = BaseRemoteDataServices<E>,
> {
dataServices: S;
/**
* User privileges payload for the current module.
* Typed as `unknown` to enforce strict type checking before consumption via RBAC utilities.
*/
privilege: unknown;
}
/**
* 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;
setMetaData: (data: TMeta) => void;
filterData: TFilter;
setFilterData: (data: TFilter) => 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>;
}
export interface EnterpriseIndexPageConfig<E extends BaseEntity = BaseEntity> {
children?: ReactNode;
showPageHeader?: boolean;
useDefaultPadding?: boolean;
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;
}
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>>;
}
export interface EnterpriseDetailPageConfig<E extends BaseEntity = BaseEntity> {
children?: ReactNode;
showPageHeader?: boolean;
useDefaultPadding?: boolean;
customHiddenActions?: (data: E | null, defaultHidden: string[]) => string[];
onCustomActionClick?: (key: ModuleActionType, data?: unknown) => void;
editMode?: 'FULL' | 'PARTIAL';
afterGetData?: (data: E) => Promise<unknown>;
}