260320:1131 Refactor Overrall #01
Build and Deploy / deploy (push) Has been cancelled

This commit is contained in:
admin
2026-03-20 11:31:27 +07:00
parent f1b81a7d0d
commit 1d3479770b
147 changed files with 1745 additions and 1567 deletions
@@ -57,15 +57,19 @@ interface Field {
options?: { label: string; value: string | number }[];
}
interface ApiError extends Error {
response?: { data?: { message?: string } };
}
interface GenericCrudTableProps<T> {
title: string;
description?: string;
entityName: string;
queryKey: any[];
queryKey: string[];
fetchFn: () => Promise<T[] | { data: T[] }>;
createFn: (data: any) => Promise<any>;
updateFn: (id: number, data: any) => Promise<any>;
deleteFn: (id: number) => Promise<any>;
createFn: (data: Record<string, unknown>) => Promise<unknown>;
updateFn: (id: number, data: Record<string, unknown>) => Promise<unknown>;
deleteFn: (id: number) => Promise<unknown>;
columns: ColumnDef<T>[];
fields: Field[];
filters?: React.ReactNode;
@@ -95,7 +99,7 @@ export function GenericCrudTable<T extends { id?: number; uuid?: string }>({
});
// ADR-019: Support both direct array or wrapped data object
const data: T[] = Array.isArray(rawData) ? rawData : (rawData as any)?.data || [];
const data: T[] = Array.isArray(rawData) ? rawData : (rawData as { data?: T[] } | undefined)?.data || [];
const createMutation = useMutation({
mutationFn: createFn,
@@ -105,13 +109,13 @@ export function GenericCrudTable<T extends { id?: number; uuid?: string }>({
setIsDialogOpen(false);
reset();
},
onError: (error: any) => {
onError: (error: ApiError) => {
toast.error(error.response?.data?.message || `Failed to create ${entityName}`);
},
});
const updateMutation = useMutation({
mutationFn: ({ id, data }: { id: number; data: any }) => updateFn(id, data),
mutationFn: ({ id, data }: { id: number; data: Record<string, unknown> }) => updateFn(id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey });
toast.success(`${entityName} updated successfully`);
@@ -119,7 +123,7 @@ export function GenericCrudTable<T extends { id?: number; uuid?: string }>({
setEditingId(null);
reset();
},
onError: (error: any) => {
onError: (error: ApiError) => {
toast.error(error.response?.data?.message || `Failed to update ${entityName}`);
},
});
@@ -131,7 +135,7 @@ export function GenericCrudTable<T extends { id?: number; uuid?: string }>({
toast.success(`${entityName} deleted successfully`);
setItemToDelete(null);
},
onError: (error: any) => {
onError: (error: ApiError) => {
toast.error(error.response?.data?.message || `Failed to delete ${entityName}`);
},
});
@@ -184,19 +188,20 @@ export function GenericCrudTable<T extends { id?: number; uuid?: string }>({
setIsDialogOpen(true);
};
const handleEdit = (item: any) => {
setEditingId(item.id);
reset(item);
const handleEdit = (item: T) => {
setEditingId(item.id as number);
reset(item as Record<string, unknown>);
// Ensure select values are strings for Shadcn Select
fields.forEach(f => {
if (f.type === 'select' && item[f.name]) {
setValue(f.name, String(item[f.name]));
const record = item as Record<string, unknown>;
if (f.type === 'select' && record[f.name]) {
setValue(f.name, String(record[f.name]));
}
});
setIsDialogOpen(true);
};
const onSubmit = (formData: any) => {
const onSubmit = (formData: Record<string, unknown>) => {
if (editingItem) {
updateMutation.mutate({ id: editingItem, data: formData });
} else {
@@ -35,13 +35,15 @@ interface RbacMatrixProps {
}
const securityService = {
getRoles: async () => {
const response = await apiClient.get<any>("/users/roles");
return response.data?.data || response.data;
getRoles: async (): Promise<Role[]> => {
const response = await apiClient.get("/users/roles");
const data = response.data?.data || response.data;
return Array.isArray(data) ? data : [];
},
getPermissions: async () => {
const response = await apiClient.get<any>("/users/permissions");
return response.data?.data || response.data;
getPermissions: async (): Promise<Permission[]> => {
const response = await apiClient.get("/users/permissions");
const data = response.data?.data || response.data;
return Array.isArray(data) ? data : [];
},
updateRolePermissions: async (roleId: number, permissionIds: number[]) => {
// This endpoint might not exist as a bulk update, usually it's per role
@@ -137,9 +139,9 @@ export function RbacMatrix() {
<div>{perm.permissionName}</div>
<div className="text-xs text-muted-foreground">{perm.description}</div>
</TableCell>
{roles.map((role: any) => {
{roles.map((role) => {
// Assume role.permissions is populated
const currentRolePerms = role.permissions?.map((p: any) => p.permissionId) || [];
const currentRolePerms = role.permissions?.map((p) => p.permissionId) || [];
const activePerms = pendingChanges[role.roleId] || currentRolePerms;
const isChecked = activePerms.includes(perm.permissionId);
+14 -4
View File
@@ -108,7 +108,7 @@ export function UserDialog({ open, onOpenChange, user }: UserDialogProps) {
isActive: user.isActive,
lineId: user.lineId || "",
primaryOrganizationId: user.primaryOrganizationId?.toString(),
roleIds: user.roles?.map((r: any) => r.roleId) || [],
roleIds: user.roles?.map((r: { roleId: number }) => r.roleId) || [],
password: "",
confirmPassword: ""
});
@@ -158,7 +158,17 @@ export function UserDialog({ open, onOpenChange, user }: UserDialogProps) {
// Create req: Password mandatory
if (!payload.password) return; // Should allow Zod to catch or show error
createUser.mutate(payload as any, {
createUser.mutate({
username: payload.username,
email: payload.email,
firstName: payload.firstName,
lastName: payload.lastName,
password: payload.password,
isActive: payload.isActive ?? true,
lineId: payload.lineId,
primaryOrganizationId: payload.primaryOrganizationId,
roleIds: payload.roleIds ?? [],
}, {
onSuccess: () => onOpenChange(false),
});
}
@@ -230,7 +240,7 @@ export function UserDialog({ open, onOpenChange, user }: UserDialogProps) {
<SelectValue placeholder="Select Organization" />
</SelectTrigger>
<SelectContent>
{organizations?.map((org: any) => (
{organizations?.map((org: { uuid: string; organizationCode: string; organizationName: string }) => (
<SelectItem
key={org.uuid}
value={org.uuid}
@@ -300,7 +310,7 @@ export function UserDialog({ open, onOpenChange, user }: UserDialogProps) {
<Label className="mb-3 block">Roles</Label>
<div className="space-y-2 border p-3 rounded-md max-h-[200px] overflow-y-auto">
{Array.isArray(roles) && roles.length === 0 && <p className="text-sm text-muted-foreground">Loading roles...</p>}
{Array.isArray(roles) && roles.map((role: any) => (
{Array.isArray(roles) && roles.map((role: { roleId: number; roleName: string; description?: string }) => (
<div key={role.roleId} className="flex items-start space-x-2">
<Checkbox
id={`role-${role.roleId}`}