feat: add StatusBadge component and integrate action confirmation modal

- Introduced StatusBadge component for displaying various status indicators with customizable colors and icons.
- Updated ModulePageHeader to conditionally render badges based on props.
- Implemented ActionConfirmationModal for handling lifecycle actions (delete, activate, etc.) with support for forms and custom body rendering.
- Enhanced EnterpriseDetailPageProvider to manage action confirmation modal state and execution of actions.
- Added new modal configurations for various actions in EnterpriseDetailPageConfig.
- Updated theme provider to include ModalsProvider and Notifications for better user feedback.
This commit is contained in:
Firman Ramdhani
2026-07-15 17:27:29 +07:00
parent 7ce4ced7d6
commit 43f9d3d491
21 changed files with 966 additions and 93 deletions
@@ -1,4 +1,6 @@
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';
@@ -219,9 +221,84 @@ export interface EnterpriseFormPageConfig<E extends BaseEntity = BaseEntity> ext
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 */
getCustomStatusConfig?: (status: string) => Partial<import('@mantine/core').BadgeProps>;
onClickCreate?: () => void;
onClickDuplicate?: (data: E) => void;
@@ -238,9 +315,14 @@ export interface EnterpriseDetailPageConfig<E extends BaseEntity = BaseEntity> e
onClickRollback?: (data: E) => void;
onClickHold?: (data: E) => void;
showHighlightData?: boolean;
showHighlightDataOnBreadcrumbs?: boolean;
highlightDataKey?: string;
// Action Modal Configurations
deleteModalConfig?: ActionModalConfig;
activateModalConfig?: ActionModalConfig;
deactivateModalConfig?: ActionModalConfig;
confirmModalConfig?: ActionModalConfig;
cancelModalConfig?: ActionModalConfig;
rollbackModalConfig?: ActionModalConfig;
holdModalConfig?: ActionModalConfig;
}
export interface PrivilegeEntity {