feat: add privileges management module with CRUD functionality

- Introduced a new Privileges module, including routes for creating, editing, and viewing privileges.
- Implemented a detailed permissions matrix for managing actions (view, create, update, delete, import) associated with each privilege.
- Added validation schema for privilege creation and editing.
- Developed UI components for displaying and managing privilege details and permissions.
- Integrated language support for English and Indonesian in the privileges module.
- Created unit tests for the privileges remote data service and transformer to ensure functionality.

This commit enhances the application by providing a comprehensive privileges management system, improving user role management and permissions handling.
This commit is contained in:
shancheas
2026-08-25 19:06:50 +07:00
parent 67ae5b6c11
commit a5a42cf87e
40 changed files with 1271 additions and 11 deletions
@@ -23,6 +23,7 @@ export enum STATUS_DATA {
INACTIVE = 'inactive',
DEACTIVATED = 'deactivated',
ARCHIVED = 'archived',
REQUEST = 'request',
REQUESTING = 'requesting',
@@ -198,6 +199,7 @@ export const DEFAULT_STATUS_MAP: Record<string, BadgeProps> = {
[STATUS_DATA.TODO]: { color: STATUS_COLOR.TODO, leftSection: getIcon(Clock) },
[STATUS_DATA.INACTIVE]: { color: STATUS_COLOR.INACTIVE, leftSection: getIcon(XCircle) },
[STATUS_DATA.ARCHIVED]: { color: STATUS_COLOR.INACTIVE, leftSection: getIcon(XCircle) },
[STATUS_DATA.DEACTIVATED]: { color: STATUS_COLOR.DEACTIVATED, leftSection: getIcon(XCircle) },
[STATUS_DATA.CLOSE]: { color: STATUS_COLOR.CLOSE, leftSection: getIcon(XCircle) },
[STATUS_DATA.CLOSED]: { color: STATUS_COLOR.CLOSED, leftSection: getIcon(XCircle) },
@@ -63,7 +63,7 @@ export function BulkActionMenu({
const hasActive = selectedRows.some((row) => row[statusKey]?.toLowerCase() === 'active');
const hasInactive = selectedRows.some(
(row) => row[statusKey]?.toLowerCase() === 'inactive' || row[statusKey]?.toLowerCase() === 'draft',
(row) => row[statusKey]?.toLowerCase() === 'inactive' || row[statusKey]?.toLowerCase() === 'draft' || row[statusKey]?.toLowerCase() === 'archived',
);
const defaultActions: PageActionProps[] = [];
@@ -6,6 +6,7 @@ export const defaultPrivileges: PrivilegeEntity = {
ALLOW_CREATE: true,
ALLOW_EDIT: true,
ALLOW_DELETE: true,
ALLOW_IMPORT: true,
ALLOW_PRINT: true,
ALLOW_PRINT_COPY: true,
@@ -29,6 +30,7 @@ export const noPrivileges: PrivilegeEntity = {
ALLOW_CREATE: false,
ALLOW_EDIT: false,
ALLOW_DELETE: false,
ALLOW_IMPORT: false,
ALLOW_PRINT: false,
ALLOW_PRINT_COPY: false,
@@ -299,6 +299,7 @@ export interface PrivilegeEntity {
ALLOW_CREATE: boolean;
ALLOW_EDIT: boolean;
ALLOW_DELETE: boolean;
ALLOW_IMPORT: boolean;
ALLOW_PRINT: boolean;
ALLOW_PRINT_COPY: boolean;
@@ -504,7 +504,7 @@ export function EnterpriseDetailPageProvider<E extends BaseEntity = BaseEntity>(
const isMasterData = moduleType === 'MASTER_DATA';
const isDataActive = detailData && ['active'].includes(detailData[statusKey]?.toLowerCase());
const isDataInActive = detailData && ['inactive', 'draft'].includes(detailData[statusKey]?.toLowerCase());
const isDataInActive = detailData && ['inactive', 'draft', 'archived'].includes(detailData[statusKey]?.toLowerCase());
// 1. Declare action with Privilege & Module Type conditions directly
const rawActions = [
@@ -21,7 +21,7 @@ import {
} from '../hooks/use-module.context';
import { StatusPage } from '../../../components';
import { useEnterpriseStorage } from '../hooks/enterprise-storage.context';
import { defaultPrivileges, noPrivileges } from '../constant';
import { noPrivileges } from '../constant';
export interface EnterpriseModuleProviderProps<
E extends BaseEntity,
@@ -47,7 +47,8 @@ export function EnterpriseModuleProvider<
// Config Slice (Static)
// ---------------------------------------------------------------------------
const storage = useEnterpriseStorage();
const [parsedPrivileges, setParsedPrivileges] = useState<PrivilegeEntity>(defaultPrivileges);
const [arePrivilegesReady, setArePrivilegesReady] = useState(false);
const [parsedPrivileges, setParsedPrivileges] = useState<PrivilegeEntity>(noPrivileges);
useEffect(() => {
const fetchPrivilegeData = async () => {
@@ -63,6 +64,8 @@ export function EnterpriseModuleProvider<
} catch (error) {
setParsedPrivileges(noPrivileges);
console.error('Failed to retrieve privilege data:', error);
} finally {
setArePrivilegesReady(true);
}
};
@@ -176,6 +179,10 @@ export function EnterpriseModuleProvider<
const { ALLOW_VIEW } = configSlice.privileges ?? {};
if (!arePrivilegesReady) {
return null;
}
if (!ALLOW_VIEW) {
return (
<StatusPage