feat: enhance confirmation dialogs and form error handling; add new utility functions
This commit is contained in:
@@ -97,13 +97,28 @@
|
||||
"hold": {
|
||||
"title": "Hold Data",
|
||||
"description": "Are you sure you want to hold this data?"
|
||||
},
|
||||
"save": {
|
||||
"title": "Save Data",
|
||||
"description": "Are you sure you want to save this data?"
|
||||
}
|
||||
},
|
||||
"draft": {
|
||||
"recoveryTitle": "Draft Found",
|
||||
"recoveryMessage": "You have an unsaved draft from {{date}}. Would you like to continue editing?",
|
||||
"continueEditing": "Continue Editing",
|
||||
"discardDraft": "Start Fresh"
|
||||
"discardDraft": "Start Fresh",
|
||||
"autoSaved": "Draft auto-saved",
|
||||
"expired": "Previous draft has expired and was discarded"
|
||||
},
|
||||
"formPage": {
|
||||
"createTitle": "Create {{module}}",
|
||||
"editTitle": "Edit {{module}}",
|
||||
"duplicateTitle": "Duplicate {{module}}",
|
||||
"loadingData": "Loading data...",
|
||||
"loadError": "Failed to load data",
|
||||
"saveError": "Failed to save data",
|
||||
"validationError": "Please fix the form errors before saving"
|
||||
},
|
||||
"bulkAction": {
|
||||
"totalData": "Total Data",
|
||||
|
||||
@@ -97,13 +97,28 @@
|
||||
"hold": {
|
||||
"title": "Hold Data",
|
||||
"description": "Apakah Anda yakin ingin menahan data ini?"
|
||||
},
|
||||
"save": {
|
||||
"title": "Simpan Data",
|
||||
"description": "Apakah Anda yakin ingin menyimpan data ini?"
|
||||
}
|
||||
},
|
||||
"draft": {
|
||||
"recoveryTitle": "Draf Ditemukan",
|
||||
"recoveryMessage": "Anda memiliki draf yang belum disimpan dari {{date}}. Apakah Anda ingin melanjutkan?",
|
||||
"continueEditing": "Lanjutkan",
|
||||
"discardDraft": "Mulai Baru"
|
||||
"discardDraft": "Mulai Baru",
|
||||
"autoSaved": "Draf tersimpan otomatis",
|
||||
"expired": "Draf sebelumnya telah kedaluwarsa dan dihapus"
|
||||
},
|
||||
"formPage": {
|
||||
"createTitle": "Buat {{module}}",
|
||||
"editTitle": "Ubah {{module}}",
|
||||
"duplicateTitle": "Duplikat {{module}}",
|
||||
"loadingData": "Memuat data...",
|
||||
"loadError": "Gagal memuat data",
|
||||
"saveError": "Gagal menyimpan data",
|
||||
"validationError": "Mohon perbaiki kesalahan formulir sebelum menyimpan"
|
||||
},
|
||||
"bulkAction": {
|
||||
"totalData": "Total Data",
|
||||
|
||||
+5
@@ -63,6 +63,11 @@ export const ACTION_TRANSLATION_MAP: Record<string, { titleKey: string; confirmK
|
||||
confirmKey: 'common:actions.hold',
|
||||
descriptionKey: 'common:confirmDialog.hold.description',
|
||||
},
|
||||
[ModuleAction.SAVE]: {
|
||||
titleKey: 'common:confirmDialog.save.title',
|
||||
confirmKey: 'common:actions.save',
|
||||
descriptionKey: 'common:confirmDialog.save.description',
|
||||
},
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -190,53 +190,6 @@ export interface TranslationSlice {
|
||||
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
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -336,10 +289,46 @@ export interface BulkActionResult {
|
||||
messages?: string[];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Page-Level Configurations
|
||||
// ---------------------------------------------------------------------------
|
||||
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 EnterpriseDetailPageConfig<E extends BaseEntity = BaseEntity> extends BasePageConfig {
|
||||
editMode?: 'FULL' | 'PARTIAL';
|
||||
customPageActions?: (data: E, actions: PageActionsProps['actions']) => PageActionsProps['actions'];
|
||||
onDetailLoaded?: (data: E) => void;
|
||||
onDataLoaded?: (data: E) => void;
|
||||
|
||||
showHighlightData?: boolean;
|
||||
showHighlightDataOnBreadcrumbs?: boolean;
|
||||
@@ -375,24 +364,24 @@ export interface EnterpriseDetailPageConfig<E extends BaseEntity = BaseEntity> e
|
||||
holdModalConfig?: ActionModalConfig;
|
||||
}
|
||||
|
||||
export interface PrivilegeEntity {
|
||||
ALLOW_VIEW: boolean;
|
||||
ALLOW_CREATE: boolean;
|
||||
ALLOW_EDIT: boolean;
|
||||
ALLOW_DELETE: boolean;
|
||||
export interface EnterpriseFormPageConfig<E extends BaseEntity = BaseEntity> extends BasePageConfig {
|
||||
formPageType: FormPageType;
|
||||
customPageActions?: (data: E, actions: PageActionsProps['actions']) => PageActionsProps['actions'];
|
||||
onDataLoaded?: (data: E) => void;
|
||||
|
||||
ALLOW_PRINT: boolean;
|
||||
ALLOW_PRINT_COPY: boolean;
|
||||
showHighlightData?: boolean;
|
||||
showHighlightDataOnBreadcrumbs?: boolean;
|
||||
highlightDataKey?: string;
|
||||
|
||||
ALLOW_APPROVAL: boolean;
|
||||
ALLOW_ACTIVATE: boolean;
|
||||
ALLOW_DEACTIVATE: boolean;
|
||||
/** 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>;
|
||||
|
||||
ALLOW_CONFIRM: boolean;
|
||||
ALLOW_CANCEL: boolean;
|
||||
ALLOW_ROLLBACK: boolean;
|
||||
ALLOW_HOLD: boolean;
|
||||
|
||||
ALLOW_LOGS: boolean;
|
||||
ALLOW_NOTES: boolean;
|
||||
/** 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)[];
|
||||
initialValueCreate?: Partial<E>;
|
||||
presetDuplicate?(payload: any): Promise<any>;
|
||||
}
|
||||
|
||||
@@ -20,17 +20,17 @@ import { shortcutsData } from '../../../constants';
|
||||
export function EnterpriseDetailPageProvider<E extends BaseEntity = BaseEntity>(props: EnterpriseDetailPageConfig<E>) {
|
||||
const {
|
||||
children,
|
||||
editMode = 'FULL',
|
||||
pageHeaderProps,
|
||||
px,
|
||||
py,
|
||||
|
||||
editMode = 'FULL',
|
||||
showHighlightData = true,
|
||||
showHighlightDataOnBreadcrumbs = true,
|
||||
highlightDataKey = 'code',
|
||||
statusKey = 'status',
|
||||
getCustomStatusBadgeConfig,
|
||||
onDetailLoaded,
|
||||
onDataLoaded,
|
||||
|
||||
customPageActions,
|
||||
onClickCreate,
|
||||
@@ -77,7 +77,7 @@ export function EnterpriseDetailPageProvider<E extends BaseEntity = BaseEntity>(
|
||||
if (response && response.data) {
|
||||
const data = response.data?.data;
|
||||
setDetailData(data as E);
|
||||
if (onDetailLoaded) onDetailLoaded(data as E);
|
||||
if (onDataLoaded) onDataLoaded(data as E);
|
||||
}
|
||||
} catch (error: any) {
|
||||
notifications.show({
|
||||
@@ -377,7 +377,7 @@ export function EnterpriseDetailPageProvider<E extends BaseEntity = BaseEntity>(
|
||||
|
||||
/** Platform-aware shortcut label for the Create action. */
|
||||
const CREATE_SHORTCUT_LABEL = useMemo(() => {
|
||||
const shortcutData = shortcutsData.find((s) => s.key === 'collapse_sidebar');
|
||||
const shortcutData = shortcutsData.find((s) => s.key === 'create_data');
|
||||
return IS_MACOS ? shortcutData?.macKeyIcons.join(' ') : shortcutData?.winKeyIcons.join(' ');
|
||||
}, [IS_MACOS]);
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ export function EnterpriseIndexPageProvider(props: EnterpriseIndexPageConfig) {
|
||||
|
||||
/** Platform-aware shortcut label for the Create action. */
|
||||
const CREATE_SHORTCUT_LABEL = useMemo(() => {
|
||||
const shortcutData = shortcutsData.find((s) => s.key === 'collapse_sidebar');
|
||||
const shortcutData = shortcutsData.find((s) => s.key === 'create_data');
|
||||
return IS_MACOS ? shortcutData?.macKeyIcons.join(' ') : shortcutData?.winKeyIcons.join(' ');
|
||||
}, [IS_MACOS]);
|
||||
|
||||
|
||||
@@ -13,12 +13,14 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.19"
|
||||
"dayjs": "^1.11.19",
|
||||
"lodash": "^4.18.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/eslint-config": "workspace:*",
|
||||
"@repo/typescript-config": "workspace:*",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/lodash": "^4.17.24",
|
||||
"eslint": "^8.57.1",
|
||||
"typescript": "5.5.4",
|
||||
"vitest": "^4.0.17"
|
||||
|
||||
@@ -4,3 +4,4 @@ export * from './encryption/encryption.utils';
|
||||
export * from './date/date.utils';
|
||||
export * from './currency/currency.utils';
|
||||
export * from './string/string.utils';
|
||||
export * from './lodash/lodash.utils';
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import lodash from 'lodash';
|
||||
|
||||
export { lodash };
|
||||
Reference in New Issue
Block a user