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
@@ -49,10 +49,7 @@ import { DEFAULT_METHODS, DESCRIPTORS, makeDefaultURLs } from './constants';
* const { data, status } = await services.getOne<BookingEntity>('42');
* ```
*/
export abstract class BaseRemoteDataServices<
E extends BaseEntity = BaseEntity,
TDTO = E,
> {
export abstract class BaseRemoteDataServices<E extends BaseEntity = BaseEntity, TDTO = E> {
/** The injected, isolated HTTP client instance. */
protected readonly httpClient: AxiosInstance;
@@ -118,7 +115,7 @@ export abstract class BaseRemoteDataServices<
'ex-module-action': action,
...(options?.config?.headers ?? {}),
},
telemetryContext: options?.telemetryContext ?? options?.config?.telemetryContext // FIXME,
telemetryContext: options?.telemetryContext ?? options?.config?.telemetryContext, // FIXME,
});
return { data: response.data, status: response.status };
@@ -149,9 +146,7 @@ export abstract class BaseRemoteDataServices<
* });
* ```
*/
async customRequest<T = unknown>(
config: AxiosRequestConfig,
): Promise<ApiResponse<T>> {
async customRequest<T = unknown>(config: AxiosRequestConfig): Promise<ApiResponse<T>> {
const response = await this.httpClient.request<T>({
...config,
headers: {
@@ -297,93 +292,63 @@ export abstract class BaseRemoteDataServices<
// ─── Data Processing Lifecycle ────────────────────────────────
/** Confirm processing of a single data record. */
confirmProcessData(id: string, config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
return this.execute<void>(DESCRIPTORS.confirmProcessData, {
confirmData(id: string, config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
return this.execute<void>(DESCRIPTORS.confirmData, {
variableURL: { id },
config,
});
}
/** Confirm processing of multiple data records. */
batchConfirmProcessData(ids: string[], config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
return this.execute<void>(DESCRIPTORS.batchConfirmProcessData, {
batchConfirmData(ids: string[], config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
return this.execute<void>(DESCRIPTORS.batchConfirmData, {
config: { ...config, data: { ids } },
});
}
/** Cancel processing of a single data record. */
cancelProcessData(id: string, config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
return this.execute<void>(DESCRIPTORS.cancelProcessData, {
cancelData(id: string, config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
return this.execute<void>(DESCRIPTORS.cancelData, {
variableURL: { id },
config,
});
}
/** Cancel processing of multiple data records. */
batchCancelProcessData(ids: string[], config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
return this.execute<void>(DESCRIPTORS.batchCancelProcessData, {
batchCancelData(ids: string[], config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
return this.execute<void>(DESCRIPTORS.batchCancelData, {
config: { ...config, data: { ids } },
});
}
// ─── Transaction Lifecycle ────────────────────────────────────
/** Confirm a transaction. */
confirmProcessTransaction(id: string, config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
return this.execute<void>(DESCRIPTORS.confirmProcessTransaction, {
variableURL: { id },
config,
});
}
/** Confirm multiple transactions. */
batchConfirmProcessTransaction(ids: string[], config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
return this.execute<void>(DESCRIPTORS.batchConfirmProcessTransaction, {
config: { ...config, data: { ids } },
});
}
/** Cancel a transaction. */
cancelProcessTransaction(id: string, config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
return this.execute<void>(DESCRIPTORS.cancelProcessTransaction, {
variableURL: { id },
config,
});
}
/** Cancel multiple transactions. */
batchCancelProcessTransaction(ids: string[], config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
return this.execute<void>(DESCRIPTORS.batchCancelProcessTransaction, {
config: { ...config, data: { ids } },
});
}
/** Rollback a transaction. */
rollbackProcessTransaction(id: string, config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
return this.execute<void>(DESCRIPTORS.rollbackProcessTransaction, {
rollbackData(id: string, config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
return this.execute<void>(DESCRIPTORS.rollbackData, {
variableURL: { id },
config,
});
}
/** Rollback multiple transactions. */
batchRollbackProcessTransaction(ids: string[], config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
return this.execute<void>(DESCRIPTORS.batchRollbackProcessTransaction, {
batchRollbackData(ids: string[], config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
return this.execute<void>(DESCRIPTORS.batchRollbackData, {
config: { ...config, data: { ids } },
});
}
/** Hold a transaction. */
holdProcessTransaction(id: string, config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
return this.execute<void>(DESCRIPTORS.holdProcessTransaction, {
holdData(id: string, config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
return this.execute<void>(DESCRIPTORS.holdData, {
variableURL: { id },
config,
});
}
/** Hold multiple transactions. */
batchHoldProcessTransaction(ids: string[], config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
return this.execute<void>(DESCRIPTORS.batchHoldProcessTransaction, {
batchHoldData(ids: string[], config?: AxiosRequestConfig): Promise<ApiResponse<void>> {
return this.execute<void>(DESCRIPTORS.batchHoldData, {
config: { ...config, data: { ids } },
});
}
@@ -11,10 +11,14 @@ export const REQUEST_ACTION = {
CREATE: 'CREATE',
EDIT: 'EDIT',
DELETE: 'DELETE',
CONFIRM_DATA: 'CONFIRM_DATA',
CANCEL_DATA: 'CANCEL_DATA',
CONFIRM_PROCESS_TRANSACTION: 'CONFIRM_PROCESS_TRANSACTION',
CANCEL_PROCESS_TRANSACTION: 'CANCEL_PROCESS_TRANSACTION',
ACTIVATE: 'ACTIVATE',
DEACTIVATE: 'DEACTIVATE',
CONFIRM: 'CONFIRM',
CANCEL: 'CANCEL',
ROLLBACK: 'ROLLBACK',
HOLD: 'HOLD',
} as const;
// ─── Default HTTP Methods ───────────────────────────────────────
@@ -36,19 +40,15 @@ export const DEFAULT_METHODS: RequestMethodMap = {
deactivateMethod: 'PATCH',
batchDeactivateMethod: 'POST',
confirmProcessDataMethod: 'PATCH',
batchConfirmProcessDataMethod: 'POST',
cancelProcessDataMethod: 'PATCH',
batchCancelProcessDataMethod: 'POST',
confirmDataMethod: 'PATCH',
batchConfirmDataMethod: 'POST',
cancelDataMethod: 'PATCH',
batchCancelDataMethod: 'POST',
confirmProcessTransactionMethod: 'PATCH',
batchConfirmProcessTransactionMethod: 'POST',
cancelProcessTransactionMethod: 'PATCH',
batchCancelProcessTransactionMethod: 'POST',
rollbackProcessTransactionMethod: 'PATCH',
batchRollbackProcessTransactionMethod: 'POST',
holdProcessTransactionMethod: 'PATCH',
batchHoldProcessTransactionMethod: 'POST',
rollbackDataMethod: 'PATCH',
batchRollbackDataMethod: 'POST',
holdDataMethod: 'PATCH',
batchHoldDataMethod: 'POST',
};
// ─── Operation Descriptors ──────────────────────────────────────
@@ -59,31 +59,63 @@ export const DEFAULT_METHODS: RequestMethodMap = {
* and action header — eliminating 22 boilerplate methods.
*/
export const DESCRIPTORS = {
getMany: { urlKey: 'getManyUrl', methodKey: 'getManyMethod', action: REQUEST_ACTION.VIEW },
getOne: { urlKey: 'getOneUrl', methodKey: 'getOneMethod', action: REQUEST_ACTION.VIEW },
create: { urlKey: 'createUrl', methodKey: 'createMethod', action: REQUEST_ACTION.CREATE },
edit: { urlKey: 'editUrl', methodKey: 'editMethod', action: REQUEST_ACTION.EDIT },
delete: { urlKey: 'deleteUrl', methodKey: 'deleteMethod', action: REQUEST_ACTION.DELETE },
getMany: { urlKey: 'getManyUrl', methodKey: 'getManyMethod', action: REQUEST_ACTION.VIEW },
getOne: { urlKey: 'getOneUrl', methodKey: 'getOneMethod', action: REQUEST_ACTION.VIEW },
create: { urlKey: 'createUrl', methodKey: 'createMethod', action: REQUEST_ACTION.CREATE },
edit: { urlKey: 'editUrl', methodKey: 'editMethod', action: REQUEST_ACTION.EDIT },
delete: { urlKey: 'deleteUrl', methodKey: 'deleteMethod', action: REQUEST_ACTION.DELETE },
batchDelete: { urlKey: 'batchDeleteUrl', methodKey: 'batchDeleteMethod', action: REQUEST_ACTION.DELETE },
activate: { urlKey: 'activateUrl', methodKey: 'activateMethod', action: REQUEST_ACTION.CONFIRM_DATA },
batchActivate: { urlKey: 'batchActivateUrl', methodKey: 'batchActivateMethod', action: REQUEST_ACTION.CONFIRM_DATA },
deactivate: { urlKey: 'deactivateUrl', methodKey: 'deactivateMethod', action: REQUEST_ACTION.CONFIRM_DATA },
batchDeactivate: { urlKey: 'batchDeactivateUrl', methodKey: 'batchDeactivateMethod', action: REQUEST_ACTION.CONFIRM_DATA },
activate: { urlKey: 'activateUrl', methodKey: 'activateMethod', action: REQUEST_ACTION.ACTIVATE },
batchActivate: { urlKey: 'batchActivateUrl', methodKey: 'batchActivateMethod', action: REQUEST_ACTION.ACTIVATE },
deactivate: { urlKey: 'deactivateUrl', methodKey: 'deactivateMethod', action: REQUEST_ACTION.DEACTIVATE },
batchDeactivate: {
urlKey: 'batchDeactivateUrl',
methodKey: 'batchDeactivateMethod',
action: REQUEST_ACTION.DEACTIVATE,
},
confirmProcessData: { urlKey: 'confirmProcessDataUrl', methodKey: 'confirmProcessDataMethod', action: REQUEST_ACTION.CONFIRM_DATA },
batchConfirmProcessData: { urlKey: 'batchConfirmProcessDataUrl', methodKey: 'batchConfirmProcessDataMethod', action: REQUEST_ACTION.CONFIRM_DATA },
cancelProcessData: { urlKey: 'cancelProcessDataUrl', methodKey: 'cancelProcessDataMethod', action: REQUEST_ACTION.CANCEL_DATA },
batchCancelProcessData: { urlKey: 'batchCancelProcessDataUrl', methodKey: 'batchCancelProcessDataMethod', action: REQUEST_ACTION.CANCEL_DATA },
confirmData: {
urlKey: 'confirmDataUrl',
methodKey: 'confirmDataMethod',
action: REQUEST_ACTION.CONFIRM,
},
batchConfirmData: {
urlKey: 'batchConfirmDataUrl',
methodKey: 'batchConfirmDataMethod',
action: REQUEST_ACTION.CONFIRM,
},
cancelData: {
urlKey: 'cancelDataUrl',
methodKey: 'cancelDataMethod',
action: REQUEST_ACTION.CANCEL,
},
batchCancelData: {
urlKey: 'batchCancelDataUrl',
methodKey: 'batchCancelDataMethod',
action: REQUEST_ACTION.CANCEL,
},
confirmProcessTransaction: { urlKey: 'confirmProcessTransactionUrl', methodKey: 'confirmProcessTransactionMethod', action: REQUEST_ACTION.CONFIRM_PROCESS_TRANSACTION },
batchConfirmProcessTransaction: { urlKey: 'batchConfirmProcessTransactionUrl', methodKey: 'batchConfirmProcessTransactionMethod', action: REQUEST_ACTION.CONFIRM_PROCESS_TRANSACTION },
cancelProcessTransaction: { urlKey: 'cancelProcessTransactionUrl', methodKey: 'cancelProcessTransactionMethod', action: REQUEST_ACTION.CANCEL_PROCESS_TRANSACTION },
batchCancelProcessTransaction: { urlKey: 'batchCancelProcessTransactionUrl', methodKey: 'batchCancelProcessTransactionMethod', action: REQUEST_ACTION.CANCEL_PROCESS_TRANSACTION },
rollbackProcessTransaction: { urlKey: 'rollbackProcessTransactionUrl', methodKey: 'rollbackProcessTransactionMethod', action: REQUEST_ACTION.CONFIRM_PROCESS_TRANSACTION },
batchRollbackProcessTransaction: { urlKey: 'batchRollbackProcessTransactionUrl', methodKey: 'batchRollbackProcessTransactionMethod', action: REQUEST_ACTION.CONFIRM_PROCESS_TRANSACTION },
holdProcessTransaction: { urlKey: 'holdProcessTransactionUrl', methodKey: 'holdProcessTransactionMethod', action: REQUEST_ACTION.CONFIRM_PROCESS_TRANSACTION },
batchHoldProcessTransaction: { urlKey: 'batchHoldProcessTransactionUrl', methodKey: 'batchHoldProcessTransactionMethod', action: REQUEST_ACTION.CONFIRM_PROCESS_TRANSACTION },
rollbackData: {
urlKey: 'rollbackDataUrl',
methodKey: 'rollbackDataMethod',
action: REQUEST_ACTION.ROLLBACK,
},
batchRollbackData: {
urlKey: 'batchRollbackDataUrl',
methodKey: 'batchRollbackDataMethod',
action: REQUEST_ACTION.ROLLBACK,
},
holdData: {
urlKey: 'holdDataUrl',
methodKey: 'holdDataMethod',
action: REQUEST_ACTION.HOLD,
},
batchHoldData: {
urlKey: 'batchHoldDataUrl',
methodKey: 'batchHoldDataMethod',
action: REQUEST_ACTION.HOLD,
},
} as const satisfies Record<string, RequestDescriptor>;
// ─── Default URL Factory ────────────────────────────────────────
@@ -109,18 +141,14 @@ export function makeDefaultURLs(apiUrl: string): ApiURLMap {
deactivateUrl: `${apiUrl}/:id/inactive`,
batchDeactivateUrl: `${apiUrl}/batch-inactive`,
confirmProcessDataUrl: `${apiUrl}/:id/confirm`,
batchConfirmProcessDataUrl: `${apiUrl}/batch-confirm`,
cancelProcessDataUrl: `${apiUrl}/:id/cancel`,
batchCancelProcessDataUrl: `${apiUrl}/batch-cancel`,
confirmDataUrl: `${apiUrl}/:id/confirm`,
batchConfirmDataUrl: `${apiUrl}/batch-confirm`,
cancelDataUrl: `${apiUrl}/:id/cancel`,
batchCancelDataUrl: `${apiUrl}/batch-cancel`,
confirmProcessTransactionUrl: `${apiUrl}/:id/confirm-data`,
batchConfirmProcessTransactionUrl: `${apiUrl}/batch-confirm-data`,
cancelProcessTransactionUrl: `${apiUrl}/:id/cancel`,
batchCancelProcessTransactionUrl: `${apiUrl}/batch-cancel`,
rollbackProcessTransactionUrl: `${apiUrl}/:id/confirm-rollback`,
batchRollbackProcessTransactionUrl: `${apiUrl}/batch-confirm-rollback`,
holdProcessTransactionUrl: `${apiUrl}/:id/confirm-hold`,
batchHoldProcessTransactionUrl: `${apiUrl}/batch-confirm-hold`,
rollbackDataUrl: `${apiUrl}/:id/rollback`,
batchRollbackDataUrl: `${apiUrl}/batch-rollback`,
holdDataUrl: `${apiUrl}/:id/hold`,
batchHoldDataUrl: `${apiUrl}/batch-hold`,
};
}
+17 -28
View File
@@ -31,19 +31,15 @@ export interface ApiURLMap {
deactivateUrl: string;
batchDeactivateUrl: string;
confirmProcessDataUrl: string;
batchConfirmProcessDataUrl: string;
cancelProcessDataUrl: string;
batchCancelProcessDataUrl: string;
confirmDataUrl: string;
batchConfirmDataUrl: string;
cancelDataUrl: string;
batchCancelDataUrl: string;
confirmProcessTransactionUrl: string;
batchConfirmProcessTransactionUrl: string;
cancelProcessTransactionUrl: string;
batchCancelProcessTransactionUrl: string;
rollbackProcessTransactionUrl: string;
batchRollbackProcessTransactionUrl: string;
holdProcessTransactionUrl: string;
batchHoldProcessTransactionUrl: string;
rollbackDataUrl: string;
batchRollbackDataUrl: string;
holdDataUrl: string;
batchHoldDataUrl: string;
}
// ─── HTTP Method Map ────────────────────────────────────────────
@@ -65,19 +61,15 @@ export interface RequestMethodMap {
deactivateMethod: string;
batchDeactivateMethod: string;
confirmProcessDataMethod: string;
batchConfirmProcessDataMethod: string;
cancelProcessDataMethod: string;
batchCancelProcessDataMethod: string;
confirmDataMethod: string;
batchConfirmDataMethod: string;
cancelDataMethod: string;
batchCancelDataMethod: string;
confirmProcessTransactionMethod: string;
batchConfirmProcessTransactionMethod: string;
cancelProcessTransactionMethod: string;
batchCancelProcessTransactionMethod: string;
rollbackProcessTransactionMethod: string;
batchRollbackProcessTransactionMethod: string;
holdProcessTransactionMethod: string;
batchHoldProcessTransactionMethod: string;
rollbackDataMethod: string;
batchRollbackDataMethod: string;
holdDataMethod: string;
batchHoldDataMethod: string;
}
// ─── Request Descriptor ─────────────────────────────────────────
@@ -117,10 +109,7 @@ export interface ExecuteOptions {
* @typeParam TEntity - The frontend domain entity type (defaults to BaseEntity)
* @typeParam TDTO - The API DTO shape (defaults to TEntity for backward compatibility)
*/
export interface DataServicesConfig<
TEntity extends BaseEntity = BaseEntity,
TDTO = TEntity,
> {
export interface DataServicesConfig<TEntity extends BaseEntity = BaseEntity, TDTO = TEntity> {
/** Base API path (e.g., '/bookings'). Used to generate all URL templates. */
apiUrl?: string;
/** Module key for the 'ex-module-key' header (e.g., 'BOOKING'). */
@@ -17,10 +17,7 @@
* // => '/orgs/a/users/b'
* ```
*/
export function interpolateUrl(
template: string,
variables?: Record<string, string>,
): string {
export function interpolateUrl(template: string, variables?: Record<string, string>): string {
if (!variables || Object.keys(variables).length === 0) {
return template;
}
@@ -32,9 +29,7 @@ export function interpolateUrl(
const key = segment.slice(1);
const value = variables[key];
if (value === undefined) {
throw new Error(
`[interpolateUrl] Missing value for URL parameter ":${key}" in template "${template}"`,
);
throw new Error(`[interpolateUrl] Missing value for URL parameter ":${key}" in template "${template}"`);
}
return encodeURIComponent(value);
}