feat: implement bulk action confirmation modal and related functionality
- Updated request methods for batch operations in constants.ts to use PUT instead of POST. - Added new translation keys for bulk actions in English and Indonesian language files. - Created BulkActionConfirmationModal component to handle bulk lifecycle actions with progress tracking and summary display. - Introduced SummaryPanel component to show aggregated results of bulk actions. - Enhanced BulkActionMenu to support custom bulk actions and improved action handling based on selected rows. - Integrated bulk action handling in EnterpriseDataTable, allowing for batch processing of selected entities. - Added new types for bulk action modal state and results in entity.ts. - Exported BulkActionConfirmationModal from the module index for accessibility.
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
import { Box } from '@mantine/core';
|
||||
|
||||
import { DataGrid, StatusBadge } from '../../../../components';
|
||||
import type { PageActionProps } from '../../../../components';
|
||||
|
||||
import {
|
||||
useEnterpriseModuleConfigContext,
|
||||
@@ -22,11 +23,21 @@ import {
|
||||
useEnterpriseModuleTranslationContext,
|
||||
useEnterpriseModuleNavigationContext,
|
||||
} from '../../hooks/use-module.context';
|
||||
import { BaseEntity } from '@repo/core-api/data-services';
|
||||
import type { BaseEntity } from '@repo/core-api/data-services';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { ModuleActionType, ModuleAction, ActionModalState, ActionModalConfig } from '../../entities/entity';
|
||||
import {
|
||||
ModuleActionType,
|
||||
ModuleAction,
|
||||
ActionModalState,
|
||||
ActionModalConfig,
|
||||
BulkActionModalState,
|
||||
BulkActionResult,
|
||||
} from '../../entities/entity';
|
||||
import { RowActionMenu } from './components/row-actions';
|
||||
import { BulkActionMenu } from './components/bulk-actions';
|
||||
import { ActionConfirmationModal, ACTION_TRANSLATION_MAP } from '../action-confirmation-modal';
|
||||
import { BulkActionConfirmationModal } from '../bulk-action-confirmation';
|
||||
import { EntityId } from '../../../../../../core-api/src/data-services/types';
|
||||
|
||||
export * from 'ag-grid-community';
|
||||
export * from 'ag-grid-react';
|
||||
@@ -89,6 +100,21 @@ export interface EnterpriseDataTableProps<E extends BaseEntity> extends Omit<AgG
|
||||
cancelModalConfig?: ActionModalConfig;
|
||||
rollbackModalConfig?: ActionModalConfig;
|
||||
holdModalConfig?: ActionModalConfig;
|
||||
|
||||
// Bulk action props
|
||||
/** Custom function to modify/extend the default bulk action buttons shown in the table header. */
|
||||
customBulkActions?: (selectedRows: any[], defaultActions: PageActionProps[]) => PageActionProps[];
|
||||
/** Maximum number of IDs per batch request during bulk operations. @default 20 */
|
||||
batchSize?: number;
|
||||
|
||||
// Bulk action custom flow callbacks (mirrors single-action onClick* pattern)
|
||||
onBulkClickDelete?: (data: E[]) => void;
|
||||
onBulkClickActivate?: (data: E[]) => void;
|
||||
onBulkClickDeactivate?: (data: E[]) => void;
|
||||
onBulkClickConfirm?: (data: E[]) => void;
|
||||
onBulkClickCancel?: (data: E[]) => void;
|
||||
onBulkClickRollback?: (data: E[]) => void;
|
||||
onBulkClickHold?: (data: E[]) => void;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -129,6 +155,17 @@ export function EnterpriseDataTable<E extends BaseEntity>(props: EnterpriseDataT
|
||||
rollbackModalConfig,
|
||||
holdModalConfig,
|
||||
|
||||
// Bulk action props
|
||||
customBulkActions,
|
||||
batchSize,
|
||||
onBulkClickDelete,
|
||||
onBulkClickActivate,
|
||||
onBulkClickDeactivate,
|
||||
onBulkClickConfirm,
|
||||
onBulkClickCancel,
|
||||
onBulkClickRollback,
|
||||
onBulkClickHold,
|
||||
|
||||
...restAgGridProps
|
||||
} = props;
|
||||
|
||||
@@ -137,7 +174,7 @@ export function EnterpriseDataTable<E extends BaseEntity>(props: EnterpriseDataT
|
||||
// ---------------------------------------------------------------------------
|
||||
const { t } = useEnterpriseModuleTranslationContext();
|
||||
const { dataServices } = useEnterpriseModuleDataServiceContext<E>();
|
||||
const { setSelectedRows, metaData, setMetaData } = useEnterpriseModuleSelectionContext<E>();
|
||||
const { selectedRows, setSelectedRows, metaData, setMetaData } = useEnterpriseModuleSelectionContext<E>();
|
||||
const navigation = useEnterpriseModuleNavigationContext();
|
||||
|
||||
const { config } = useEnterpriseModuleConfigContext();
|
||||
@@ -262,6 +299,138 @@ export function EnterpriseDataTable<E extends BaseEntity>(props: EnterpriseDataT
|
||||
[dataServices, closeActionModal, modalConfigMap, t],
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Bulk Action Modal State & Handlers
|
||||
// ---------------------------------------------------------------------------
|
||||
const CLOSED_BULK_MODAL: BulkActionModalState<E> = useMemo(() => ({ opened: false, action: null, data: [] }), []);
|
||||
const [bulkModalState, setBulkModalState] = useState<BulkActionModalState<E>>(CLOSED_BULK_MODAL);
|
||||
|
||||
const openBulkActionModal = useCallback(
|
||||
(action: ModuleActionType, data: E[]) => {
|
||||
const config = modalConfigMap[action as keyof typeof modalConfigMap];
|
||||
setBulkModalState({ opened: true, action, data, config });
|
||||
},
|
||||
[modalConfigMap],
|
||||
);
|
||||
|
||||
const closeBulkActionModal = useCallback(() => {
|
||||
setBulkModalState(CLOSED_BULK_MODAL);
|
||||
|
||||
// Refresh grid data after bulk action completes
|
||||
if (gridApiRef.current) {
|
||||
gridApiRef.current.refreshServerSide({ purge: false });
|
||||
}
|
||||
|
||||
// Clear selected rows
|
||||
setSelectedRows([]);
|
||||
if (gridApiRef.current) {
|
||||
gridApiRef.current.deselectAll();
|
||||
}
|
||||
}, [CLOSED_BULK_MODAL, setSelectedRows]);
|
||||
|
||||
const handleBulkActionClick = useCallback(
|
||||
(action: ModuleActionType, data: E[]) => {
|
||||
switch (action) {
|
||||
case ModuleAction.DELETE:
|
||||
if (onBulkClickDelete) onBulkClickDelete(data);
|
||||
else openBulkActionModal(action, data);
|
||||
break;
|
||||
case ModuleAction.ACTIVATE:
|
||||
if (onBulkClickActivate) onBulkClickActivate(data);
|
||||
else openBulkActionModal(action, data);
|
||||
break;
|
||||
case ModuleAction.DEACTIVATE:
|
||||
if (onBulkClickDeactivate) onBulkClickDeactivate(data);
|
||||
else openBulkActionModal(action, data);
|
||||
break;
|
||||
case ModuleAction.CONFIRM:
|
||||
if (onBulkClickConfirm) onBulkClickConfirm(data);
|
||||
else openBulkActionModal(action, data);
|
||||
break;
|
||||
case ModuleAction.CANCEL:
|
||||
if (onBulkClickCancel) onBulkClickCancel(data);
|
||||
else openBulkActionModal(action, data);
|
||||
break;
|
||||
case ModuleAction.ROLLBACK:
|
||||
if (onBulkClickRollback) onBulkClickRollback(data);
|
||||
else openBulkActionModal(action, data);
|
||||
break;
|
||||
case ModuleAction.HOLD:
|
||||
if (onBulkClickHold) onBulkClickHold(data);
|
||||
else openBulkActionModal(action, data);
|
||||
break;
|
||||
default:
|
||||
openBulkActionModal(action, data);
|
||||
break;
|
||||
}
|
||||
},
|
||||
[
|
||||
openBulkActionModal,
|
||||
onBulkClickDelete,
|
||||
onBulkClickActivate,
|
||||
onBulkClickDeactivate,
|
||||
onBulkClickConfirm,
|
||||
onBulkClickCancel,
|
||||
onBulkClickRollback,
|
||||
onBulkClickHold,
|
||||
],
|
||||
);
|
||||
|
||||
/**
|
||||
* Executes a single batch chunk of the bulk action.
|
||||
* Called by BulkActionConfirmationModal per chunk.
|
||||
*/
|
||||
const executeBulkAction = useCallback(
|
||||
async (action: ModuleActionType, ids: EntityId[], meta?: Record<string, unknown>): Promise<BulkActionResult> => {
|
||||
try {
|
||||
switch (action) {
|
||||
case ModuleAction.DELETE:
|
||||
await dataServices.batchDelete(ids, meta);
|
||||
break;
|
||||
case ModuleAction.ACTIVATE:
|
||||
await dataServices.batchActivate(ids, meta);
|
||||
break;
|
||||
case ModuleAction.DEACTIVATE:
|
||||
await dataServices.batchDeactivate(ids, meta);
|
||||
break;
|
||||
case ModuleAction.CONFIRM:
|
||||
await dataServices.batchConfirmData(ids, meta);
|
||||
break;
|
||||
case ModuleAction.CANCEL:
|
||||
await dataServices.batchCancelData(ids, meta);
|
||||
break;
|
||||
case ModuleAction.ROLLBACK:
|
||||
await dataServices.batchRollbackData(ids, meta);
|
||||
break;
|
||||
case ModuleAction.HOLD:
|
||||
await dataServices.batchHoldData(ids, meta);
|
||||
break;
|
||||
default:
|
||||
console.warn(`[executeBulkAction] Unhandled action: ${action}`);
|
||||
return { total_items: ids.length, total_success: 0, total_failed: ids.length };
|
||||
}
|
||||
|
||||
return {
|
||||
total_items: ids.length,
|
||||
total_success: ids.length,
|
||||
total_failed: 0,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
total_items: ids.length,
|
||||
total_success: 0,
|
||||
total_failed: ids.length,
|
||||
messages: [error?.message || 'Unknown error'],
|
||||
};
|
||||
}
|
||||
},
|
||||
[dataServices],
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Action Handlers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const handleActionClick = useCallback(
|
||||
(action: ModuleActionType | 'VIEW', data: E) => {
|
||||
switch (action) {
|
||||
@@ -521,6 +690,14 @@ export function EnterpriseDataTable<E extends BaseEntity>(props: EnterpriseDataT
|
||||
// ---------------------------------------------------------------------------
|
||||
return (
|
||||
<Box>
|
||||
{/* BULK ACTION TOOLBAR — shown when rows are selected */}
|
||||
<BulkActionMenu
|
||||
selectedRows={selectedRows as E[]}
|
||||
onActionClick={handleBulkActionClick as (action: ModuleActionType, data: any[]) => void}
|
||||
statusKey={statusKey}
|
||||
customBulkActions={customBulkActions}
|
||||
/>
|
||||
|
||||
{/* GRID CONTAINER */}
|
||||
<Box
|
||||
className="erp-data-grid-container"
|
||||
@@ -552,13 +729,22 @@ export function EnterpriseDataTable<E extends BaseEntity>(props: EnterpriseDataT
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Action Confirmation Modal */}
|
||||
{/* Single-Row Action Confirmation Modal */}
|
||||
<ActionConfirmationModal<E>
|
||||
modalState={actionModalState}
|
||||
onClose={closeActionModal}
|
||||
onExecute={executeAction}
|
||||
t={t}
|
||||
/>
|
||||
|
||||
{/* Bulk Action Confirmation Modal */}
|
||||
<BulkActionConfirmationModal<E>
|
||||
modalState={bulkModalState}
|
||||
onClose={closeBulkActionModal}
|
||||
onExecute={executeBulkAction}
|
||||
t={t}
|
||||
batchSize={batchSize}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user