feat: add isActive status support to roles with filtering and management UI
This commit is contained in:
parent
f480979020
commit
b0fe466a3d
@ -10,6 +10,7 @@ import {
|
|||||||
PrimaryButton,
|
PrimaryButton,
|
||||||
SecondaryButton,
|
SecondaryButton,
|
||||||
FormTextArea,
|
FormTextArea,
|
||||||
|
FormSelect,
|
||||||
} from "@/components/shared";
|
} from "@/components/shared";
|
||||||
import type { Role, UpdateRoleRequest } from "@/types/role";
|
import type { Role, UpdateRoleRequest } from "@/types/role";
|
||||||
import { useAppSelector } from "@/hooks/redux-hooks";
|
import { useAppSelector } from "@/hooks/redux-hooks";
|
||||||
@ -74,6 +75,7 @@ const editRoleSchema = z.object({
|
|||||||
"Code must be lowercase and use '_' for separation (e.g. abc_def)",
|
"Code must be lowercase and use '_' for separation (e.g. abc_def)",
|
||||||
),
|
),
|
||||||
description: z.string().min(1, "Description is required"),
|
description: z.string().min(1, "Description is required"),
|
||||||
|
is_active: z.boolean(),
|
||||||
permissions: z
|
permissions: z
|
||||||
.array(
|
.array(
|
||||||
z.object({
|
z.object({
|
||||||
@ -124,7 +126,7 @@ export const EditRoleModal = ({
|
|||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
setValue,
|
setValue,
|
||||||
// watch,
|
watch,
|
||||||
reset,
|
reset,
|
||||||
setError,
|
setError,
|
||||||
clearErrors,
|
clearErrors,
|
||||||
@ -132,10 +134,13 @@ export const EditRoleModal = ({
|
|||||||
} = useForm<EditRoleFormData>({
|
} = useForm<EditRoleFormData>({
|
||||||
resolver: zodResolver(editRoleSchema),
|
resolver: zodResolver(editRoleSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
|
is_active: true,
|
||||||
permissions: [],
|
permissions: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const statusValue = watch("is_active");
|
||||||
|
|
||||||
// const nameValue = watch("name");
|
// const nameValue = watch("name");
|
||||||
|
|
||||||
// Auto-generate code from name - Only during creation (handled in parent or different component)
|
// Auto-generate code from name - Only during creation (handled in parent or different component)
|
||||||
@ -310,6 +315,7 @@ export const EditRoleModal = ({
|
|||||||
name: role.name,
|
name: role.name,
|
||||||
code: role.code,
|
code: role.code,
|
||||||
description: role.description || "",
|
description: role.description || "",
|
||||||
|
is_active: role.is_active ?? true,
|
||||||
permissions: rolePermissions,
|
permissions: rolePermissions,
|
||||||
});
|
});
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@ -331,6 +337,7 @@ export const EditRoleModal = ({
|
|||||||
name: "",
|
name: "",
|
||||||
code: "",
|
code: "",
|
||||||
description: "",
|
description: "",
|
||||||
|
is_active: true,
|
||||||
permissions: [],
|
permissions: [],
|
||||||
});
|
});
|
||||||
setLoadError(null);
|
setLoadError(null);
|
||||||
@ -505,6 +512,20 @@ export const EditRoleModal = ({
|
|||||||
rows={4}
|
rows={4}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<div className="pb-4">
|
||||||
|
<FormSelect
|
||||||
|
label="Status"
|
||||||
|
required
|
||||||
|
options={[
|
||||||
|
{ value: "true", label: "Active" },
|
||||||
|
{ value: "false", label: "Inactive" },
|
||||||
|
]}
|
||||||
|
value={String(statusValue)}
|
||||||
|
onValueChange={(value) => setValue("is_active", value === "true", { shouldValidate: true })}
|
||||||
|
error={errors.is_active?.message}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Permissions Section */}
|
{/* Permissions Section */}
|
||||||
<div className="pb-4">
|
<div className="pb-4">
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -153,8 +153,8 @@ export const EditUserModal = ({
|
|||||||
// Load roles for dropdown - ensure selected role is included
|
// Load roles for dropdown - ensure selected role is included
|
||||||
const loadRoles = async (page: number, limit: number) => {
|
const loadRoles = async (page: number, limit: number) => {
|
||||||
const response = defaultTenantId
|
const response = defaultTenantId
|
||||||
? await roleService.getByTenant(defaultTenantId, page, limit)
|
? await roleService.getByTenant(defaultTenantId, page, limit, undefined, undefined, true)
|
||||||
: await roleService.getAll(page, limit);
|
: await roleService.getAll(page, limit, undefined, undefined, undefined, true);
|
||||||
return {
|
return {
|
||||||
options: response.data.map((role) => ({
|
options: response.data.map((role) => ({
|
||||||
value: role.id,
|
value: role.id,
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import {
|
|||||||
PrimaryButton,
|
PrimaryButton,
|
||||||
SecondaryButton,
|
SecondaryButton,
|
||||||
FormTextArea,
|
FormTextArea,
|
||||||
|
FormSelect,
|
||||||
} from "@/components/shared";
|
} from "@/components/shared";
|
||||||
import type { CreateRoleRequest } from "@/types/role";
|
import type { CreateRoleRequest } from "@/types/role";
|
||||||
import { useAppSelector } from "@/hooks/redux-hooks";
|
import { useAppSelector } from "@/hooks/redux-hooks";
|
||||||
@ -74,6 +75,7 @@ const newRoleSchema = z.object({
|
|||||||
"Code must be lowercase and use '_' for separation (e.g. abc_def)",
|
"Code must be lowercase and use '_' for separation (e.g. abc_def)",
|
||||||
),
|
),
|
||||||
description: z.string().min(1, "Description is required"),
|
description: z.string().min(1, "Description is required"),
|
||||||
|
is_active: z.boolean(),
|
||||||
permissions: z
|
permissions: z
|
||||||
.array(
|
.array(
|
||||||
z.object({
|
z.object({
|
||||||
@ -125,10 +127,13 @@ export const NewRoleModal = ({
|
|||||||
resolver: zodResolver(newRoleSchema),
|
resolver: zodResolver(newRoleSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
code: undefined,
|
code: undefined,
|
||||||
|
is_active: true,
|
||||||
permissions: [],
|
permissions: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const statusValue = watch("is_active");
|
||||||
|
|
||||||
const nameValue = watch("name");
|
const nameValue = watch("name");
|
||||||
|
|
||||||
// Auto-generate code from name
|
// Auto-generate code from name
|
||||||
@ -146,6 +151,7 @@ export const NewRoleModal = ({
|
|||||||
name: "",
|
name: "",
|
||||||
code: undefined,
|
code: undefined,
|
||||||
description: "",
|
description: "",
|
||||||
|
is_active: true,
|
||||||
permissions: [],
|
permissions: [],
|
||||||
});
|
});
|
||||||
setSelectedPermissions([]);
|
setSelectedPermissions([]);
|
||||||
@ -153,6 +159,11 @@ export const NewRoleModal = ({
|
|||||||
}
|
}
|
||||||
}, [isOpen, reset, clearErrors]);
|
}, [isOpen, reset, clearErrors]);
|
||||||
|
|
||||||
|
const statusOptions = [
|
||||||
|
{ value: "true", label: "Active" },
|
||||||
|
{ value: "false", label: "Inactive" },
|
||||||
|
];
|
||||||
|
|
||||||
// Build available resources and actions based on user permissions
|
// Build available resources and actions based on user permissions
|
||||||
const availableResourcesAndActions = useMemo(() => {
|
const availableResourcesAndActions = useMemo(() => {
|
||||||
const resourceMap = new Map<string, Set<string>>();
|
const resourceMap = new Map<string, Set<string>>();
|
||||||
@ -395,6 +406,17 @@ export const NewRoleModal = ({
|
|||||||
rows={4}
|
rows={4}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<div className="pb-4">
|
||||||
|
<FormSelect
|
||||||
|
label="Status"
|
||||||
|
required
|
||||||
|
options={statusOptions}
|
||||||
|
value={String(statusValue)}
|
||||||
|
onValueChange={(value) => setValue("is_active", value === "true", { shouldValidate: true })}
|
||||||
|
error={errors.is_active?.message}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Permissions Section */}
|
{/* Permissions Section */}
|
||||||
<div className="pb-4">
|
<div className="pb-4">
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -126,8 +126,8 @@ export const NewUserModal = ({
|
|||||||
|
|
||||||
const loadRoles = async (page: number, limit: number) => {
|
const loadRoles = async (page: number, limit: number) => {
|
||||||
const response = defaultTenantId
|
const response = defaultTenantId
|
||||||
? await roleService.getByTenant(defaultTenantId, page, limit)
|
? await roleService.getByTenant(defaultTenantId, page, limit, undefined, undefined, true)
|
||||||
: await roleService.getAll(page, limit);
|
: await roleService.getAll(page, limit, undefined, undefined, undefined, true);
|
||||||
return {
|
return {
|
||||||
options: response.data.map((role) => ({ value: role.id, label: role.name })),
|
options: response.data.map((role) => ({ value: role.id, label: role.name })),
|
||||||
pagination: response.pagination,
|
pagination: response.pagination,
|
||||||
|
|||||||
@ -116,6 +116,14 @@ export const ViewRoleModal = ({
|
|||||||
</StatusBadge>
|
</StatusBadge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-xs font-medium text-[#6b7280] mb-1 block">Status</label>
|
||||||
|
<div className="mt-1">
|
||||||
|
<StatusBadge variant={role.is_active !== false ? 'success' : 'failure'}>
|
||||||
|
{role.is_active !== false ? 'Active' : 'Inactive'}
|
||||||
|
</StatusBadge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs font-medium text-[#6b7280] mb-1 block">Role ID</label>
|
<label className="text-xs font-medium text-[#6b7280] mb-1 block">Role ID</label>
|
||||||
<p className="text-sm text-[#0e1b2a] font-mono">{role.id}</p>
|
<p className="text-sm text-[#0e1b2a] font-mono">{role.id}</p>
|
||||||
|
|||||||
@ -22,6 +22,8 @@ import {
|
|||||||
Modal,
|
Modal,
|
||||||
FormField,
|
FormField,
|
||||||
FormTextArea,
|
FormTextArea,
|
||||||
|
FormSelect,
|
||||||
|
StatusBadge,
|
||||||
// DeleteConfirmationModal,
|
// DeleteConfirmationModal,
|
||||||
type Column,
|
type Column,
|
||||||
} from "@/components/shared";
|
} from "@/components/shared";
|
||||||
@ -99,6 +101,7 @@ const newPlatformRoleSchema = z.object({
|
|||||||
"Code must be lowercase and use '_' for separation (e.g. abc_def)",
|
"Code must be lowercase and use '_' for separation (e.g. abc_def)",
|
||||||
),
|
),
|
||||||
description: z.string().min(1, "Description is required"),
|
description: z.string().min(1, "Description is required"),
|
||||||
|
is_active: z.boolean(),
|
||||||
permissions: z
|
permissions: z
|
||||||
.array(
|
.array(
|
||||||
z.object({
|
z.object({
|
||||||
@ -123,6 +126,7 @@ const editPlatformRoleSchema = z.object({
|
|||||||
"Code must be lowercase and use '_' for separation (e.g. abc_def)",
|
"Code must be lowercase and use '_' for separation (e.g. abc_def)",
|
||||||
),
|
),
|
||||||
description: z.string().min(1, "Description is required"),
|
description: z.string().min(1, "Description is required"),
|
||||||
|
is_active: z.boolean(),
|
||||||
permissions: z
|
permissions: z
|
||||||
.array(
|
.array(
|
||||||
z.object({
|
z.object({
|
||||||
@ -168,11 +172,13 @@ const NewPlatformRoleModal = ({
|
|||||||
resolver: zodResolver(newPlatformRoleSchema),
|
resolver: zodResolver(newPlatformRoleSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
code: undefined,
|
code: undefined,
|
||||||
|
is_active: true,
|
||||||
permissions: [],
|
permissions: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const nameValue = watch("name");
|
const nameValue = watch("name");
|
||||||
|
const statusValue = watch("is_active");
|
||||||
|
|
||||||
// Auto-generate code from name
|
// Auto-generate code from name
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -189,6 +195,7 @@ const NewPlatformRoleModal = ({
|
|||||||
name: "",
|
name: "",
|
||||||
code: undefined,
|
code: undefined,
|
||||||
description: "",
|
description: "",
|
||||||
|
is_active: true,
|
||||||
permissions: [],
|
permissions: [],
|
||||||
});
|
});
|
||||||
setSelectedPermissions([]);
|
setSelectedPermissions([]);
|
||||||
@ -381,6 +388,20 @@ const NewPlatformRoleModal = ({
|
|||||||
rows={4}
|
rows={4}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<div className="pb-4">
|
||||||
|
<FormSelect
|
||||||
|
label="Status"
|
||||||
|
required
|
||||||
|
options={[
|
||||||
|
{ value: "true", label: "Active" },
|
||||||
|
{ value: "false", label: "Inactive" },
|
||||||
|
]}
|
||||||
|
value={String(statusValue)}
|
||||||
|
onValueChange={(value) => setValue("is_active", value === "true", { shouldValidate: true })}
|
||||||
|
error={errors.is_active?.message}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="pb-4">
|
<div className="pb-4">
|
||||||
<div className="flex flex-col items-start gap-3 self-stretch p-4 rounded-[8px] border border-[#D1D5DB] bg-white">
|
<div className="flex flex-col items-start gap-3 self-stretch p-4 rounded-[8px] border border-[#D1D5DB] bg-white">
|
||||||
<div className="flex flex-col items-start self-stretch">
|
<div className="flex flex-col items-start self-stretch">
|
||||||
@ -510,6 +531,7 @@ const EditPlatformRoleModal = ({
|
|||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
setValue,
|
setValue,
|
||||||
|
watch,
|
||||||
reset,
|
reset,
|
||||||
setError,
|
setError,
|
||||||
clearErrors,
|
clearErrors,
|
||||||
@ -517,10 +539,13 @@ const EditPlatformRoleModal = ({
|
|||||||
} = useForm<EditPlatformRoleFormData>({
|
} = useForm<EditPlatformRoleFormData>({
|
||||||
resolver: zodResolver(editPlatformRoleSchema),
|
resolver: zodResolver(editPlatformRoleSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
|
is_active: true,
|
||||||
permissions: [],
|
permissions: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const statusValue = watch("is_active");
|
||||||
|
|
||||||
// Build available resources and actions based on user permissions
|
// Build available resources and actions based on user permissions
|
||||||
const availableResourcesAndActions = useMemo(() => {
|
const availableResourcesAndActions = useMemo(() => {
|
||||||
const resourceMap = new Map<string, Set<string>>();
|
const resourceMap = new Map<string, Set<string>>();
|
||||||
@ -608,6 +633,7 @@ const EditPlatformRoleModal = ({
|
|||||||
name: role.name,
|
name: role.name,
|
||||||
code: role.code,
|
code: role.code,
|
||||||
description: role.description || "",
|
description: role.description || "",
|
||||||
|
is_active: role.is_active ?? true,
|
||||||
permissions: rolePermissions,
|
permissions: rolePermissions,
|
||||||
});
|
});
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@ -628,6 +654,7 @@ const EditPlatformRoleModal = ({
|
|||||||
name: "",
|
name: "",
|
||||||
code: "",
|
code: "",
|
||||||
description: "",
|
description: "",
|
||||||
|
is_active: true,
|
||||||
permissions: [],
|
permissions: [],
|
||||||
});
|
});
|
||||||
setLoadError(null);
|
setLoadError(null);
|
||||||
@ -769,6 +796,20 @@ const EditPlatformRoleModal = ({
|
|||||||
rows={4}
|
rows={4}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<div className="pb-4">
|
||||||
|
<FormSelect
|
||||||
|
label="Status"
|
||||||
|
required
|
||||||
|
options={[
|
||||||
|
{ value: "true", label: "Active" },
|
||||||
|
{ value: "false", label: "Inactive" },
|
||||||
|
]}
|
||||||
|
value={String(statusValue)}
|
||||||
|
onValueChange={(value) => setValue("is_active", value === "true", { shouldValidate: true })}
|
||||||
|
error={errors.is_active?.message}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="pb-4">
|
<div className="pb-4">
|
||||||
<div className="flex flex-col items-start gap-3 self-stretch p-4 rounded-[8px] border border-[#D1D5DB] bg-white">
|
<div className="flex flex-col items-start gap-3 self-stretch p-4 rounded-[8px] border border-[#D1D5DB] bg-white">
|
||||||
<div className="flex flex-col items-start self-stretch">
|
<div className="flex flex-col items-start self-stretch">
|
||||||
@ -909,6 +950,7 @@ export const PlatformRolesTable = forwardRef<PlatformRolesTableRef, {}>(
|
|||||||
|
|
||||||
// Filter and Search state
|
// Filter and Search state
|
||||||
const [orderBy, setOrderBy] = useState<string[] | null>(null);
|
const [orderBy, setOrderBy] = useState<string[] | null>(null);
|
||||||
|
const [statusFilter, setStatusFilter] = useState<string | null>(null);
|
||||||
const [search, setSearch] = useState<string>("");
|
const [search, setSearch] = useState<string>("");
|
||||||
const [debouncedSearch, setDebouncedSearch] = useState<string>("");
|
const [debouncedSearch, setDebouncedSearch] = useState<string>("");
|
||||||
|
|
||||||
@ -924,7 +966,7 @@ export const PlatformRolesTable = forwardRef<PlatformRolesTableRef, {}>(
|
|||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
openNewModal: () => setIsCreateOpen(true),
|
openNewModal: () => setIsCreateOpen(true),
|
||||||
refresh: () =>
|
refresh: () =>
|
||||||
fetchPlatformRoles(currentPage, limit, orderBy, debouncedSearch),
|
fetchPlatformRoles(currentPage, limit, orderBy, debouncedSearch, statusFilter),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const fetchPlatformRoles = async (
|
const fetchPlatformRoles = async (
|
||||||
@ -932,10 +974,12 @@ export const PlatformRolesTable = forwardRef<PlatformRolesTableRef, {}>(
|
|||||||
itemsPerPage: number,
|
itemsPerPage: number,
|
||||||
sortBy: string[] | null = null,
|
sortBy: string[] | null = null,
|
||||||
searchQuery: string | null = null,
|
searchQuery: string | null = null,
|
||||||
|
statusValStr: string | null = null,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
const statusVal = statusValStr === "true" ? true : statusValStr === "false" ? false : null;
|
||||||
// Load roles filtered by scope=platform
|
// Load roles filtered by scope=platform
|
||||||
const response = await roleService.getAll(
|
const response = await roleService.getAll(
|
||||||
page,
|
page,
|
||||||
@ -943,6 +987,7 @@ export const PlatformRolesTable = forwardRef<PlatformRolesTableRef, {}>(
|
|||||||
sortBy,
|
sortBy,
|
||||||
searchQuery,
|
searchQuery,
|
||||||
"platform",
|
"platform",
|
||||||
|
statusVal,
|
||||||
);
|
);
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
setRoles(response.data);
|
setRoles(response.data);
|
||||||
@ -970,8 +1015,8 @@ export const PlatformRolesTable = forwardRef<PlatformRolesTableRef, {}>(
|
|||||||
}, [search]);
|
}, [search]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchPlatformRoles(currentPage, limit, orderBy, debouncedSearch);
|
fetchPlatformRoles(currentPage, limit, orderBy, debouncedSearch, statusFilter);
|
||||||
}, [currentPage, limit, orderBy, debouncedSearch]);
|
}, [currentPage, limit, orderBy, debouncedSearch, statusFilter]);
|
||||||
|
|
||||||
const handleCreateRole = async (data: CreateRoleRequest) => {
|
const handleCreateRole = async (data: CreateRoleRequest) => {
|
||||||
try {
|
try {
|
||||||
@ -986,7 +1031,7 @@ export const PlatformRolesTable = forwardRef<PlatformRolesTableRef, {}>(
|
|||||||
response.message || "Platform role created successfully",
|
response.message || "Platform role created successfully",
|
||||||
);
|
);
|
||||||
setIsCreateOpen(false);
|
setIsCreateOpen(false);
|
||||||
fetchPlatformRoles(currentPage, limit, orderBy, debouncedSearch);
|
fetchPlatformRoles(currentPage, limit, orderBy, debouncedSearch, statusFilter);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
throw err;
|
throw err;
|
||||||
} finally {
|
} finally {
|
||||||
@ -1008,7 +1053,7 @@ export const PlatformRolesTable = forwardRef<PlatformRolesTableRef, {}>(
|
|||||||
);
|
);
|
||||||
setEditModalOpen(false);
|
setEditModalOpen(false);
|
||||||
setSelectedRoleId(null);
|
setSelectedRoleId(null);
|
||||||
fetchPlatformRoles(currentPage, limit, orderBy, debouncedSearch);
|
fetchPlatformRoles(currentPage, limit, orderBy, debouncedSearch, statusFilter);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
throw err;
|
throw err;
|
||||||
} finally {
|
} finally {
|
||||||
@ -1064,6 +1109,15 @@ export const PlatformRolesTable = forwardRef<PlatformRolesTableRef, {}>(
|
|||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "is_active",
|
||||||
|
label: "Status",
|
||||||
|
render: (role) => (
|
||||||
|
<StatusBadge variant={role.is_active !== false ? "success" : "failure"}>
|
||||||
|
{role.is_active !== false ? "Active" : "Inactive"}
|
||||||
|
</StatusBadge>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "user_count",
|
key: "user_count",
|
||||||
label: "Assigned Users",
|
label: "Assigned Users",
|
||||||
@ -1120,6 +1174,20 @@ export const PlatformRolesTable = forwardRef<PlatformRolesTableRef, {}>(
|
|||||||
placeholder="Search platform roles..."
|
placeholder="Search platform roles..."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<FilterDropdown
|
||||||
|
label="Status"
|
||||||
|
options={[
|
||||||
|
{ value: "true", label: "Active" },
|
||||||
|
{ value: "false", label: "Inactive" },
|
||||||
|
]}
|
||||||
|
value={statusFilter}
|
||||||
|
onChange={(value) => {
|
||||||
|
setStatusFilter(Array.isArray(value) ? null : value || null);
|
||||||
|
setCurrentPage(1);
|
||||||
|
}}
|
||||||
|
placeholder="All Statuses"
|
||||||
|
/>
|
||||||
|
|
||||||
<FilterDropdown
|
<FilterDropdown
|
||||||
label="Sort by"
|
label="Sort by"
|
||||||
options={[
|
options={[
|
||||||
|
|||||||
@ -683,6 +683,7 @@ export const PlatformUsersTable = forwardRef<PlatformUsersTableRef>(
|
|||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
"platform",
|
"platform",
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
setRoles(response.data);
|
setRoles(response.data);
|
||||||
|
|||||||
@ -63,12 +63,6 @@ export const RolesTable = forwardRef<RolesTableRef, RolesTableProps>(
|
|||||||
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
|
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
|
||||||
const [isCreating, setIsCreating] = useState<boolean>(false);
|
const [isCreating, setIsCreating] = useState<boolean>(false);
|
||||||
|
|
||||||
// Expose imperative methods
|
|
||||||
useImperativeHandle(ref, () => ({
|
|
||||||
openNewModal: () => setIsModalOpen(true),
|
|
||||||
refresh: () => fetchRoles(currentPage, limit, orderBy, debouncedSearch),
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Pagination state
|
// Pagination state
|
||||||
const [currentPage, setCurrentPage] = useState<number>(1);
|
const [currentPage, setCurrentPage] = useState<number>(1);
|
||||||
const [limit, setLimit] = useState<number>(5);
|
const [limit, setLimit] = useState<number>(5);
|
||||||
@ -89,6 +83,13 @@ export const RolesTable = forwardRef<RolesTableRef, RolesTableProps>(
|
|||||||
// Filter state
|
// Filter state
|
||||||
// const [scopeFilter, setScopeFilter] = useState<string | null>(null);
|
// const [scopeFilter, setScopeFilter] = useState<string | null>(null);
|
||||||
const [orderBy, setOrderBy] = useState<string[] | null>(null);
|
const [orderBy, setOrderBy] = useState<string[] | null>(null);
|
||||||
|
const [statusFilter, setStatusFilter] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Expose imperative methods
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
openNewModal: () => setIsModalOpen(true),
|
||||||
|
refresh: () => fetchRoles(currentPage, limit, orderBy, debouncedSearch, statusFilter),
|
||||||
|
}));
|
||||||
|
|
||||||
// Search state
|
// Search state
|
||||||
const [search, setSearch] = useState<string>("");
|
const [search, setSearch] = useState<string>("");
|
||||||
@ -106,13 +107,14 @@ export const RolesTable = forwardRef<RolesTableRef, RolesTableProps>(
|
|||||||
const fetchRoles = async (
|
const fetchRoles = async (
|
||||||
page: number,
|
page: number,
|
||||||
itemsPerPage: number,
|
itemsPerPage: number,
|
||||||
// scope: string | null = null,
|
|
||||||
sortBy: string[] | null = null,
|
sortBy: string[] | null = null,
|
||||||
searchQuery: string | null = null,
|
searchQuery: string | null = null,
|
||||||
|
statusValStr: string | null = null,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
const statusVal = statusValStr === "true" ? true : statusValStr === "false" ? false : null;
|
||||||
const response = tenantId
|
const response = tenantId
|
||||||
? await roleService.getByTenant(
|
? await roleService.getByTenant(
|
||||||
tenantId,
|
tenantId,
|
||||||
@ -120,8 +122,16 @@ export const RolesTable = forwardRef<RolesTableRef, RolesTableProps>(
|
|||||||
itemsPerPage,
|
itemsPerPage,
|
||||||
sortBy,
|
sortBy,
|
||||||
searchQuery,
|
searchQuery,
|
||||||
|
statusVal,
|
||||||
)
|
)
|
||||||
: await roleService.getAll(page, itemsPerPage, sortBy, searchQuery);
|
: await roleService.getAll(
|
||||||
|
page,
|
||||||
|
itemsPerPage,
|
||||||
|
sortBy,
|
||||||
|
searchQuery,
|
||||||
|
null,
|
||||||
|
statusVal,
|
||||||
|
);
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
setRoles(response.data);
|
setRoles(response.data);
|
||||||
setPagination(response.pagination);
|
setPagination(response.pagination);
|
||||||
@ -145,8 +155,8 @@ export const RolesTable = forwardRef<RolesTableRef, RolesTableProps>(
|
|||||||
}, [search]);
|
}, [search]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchRoles(currentPage, limit, orderBy, debouncedSearch);
|
fetchRoles(currentPage, limit, orderBy, debouncedSearch, statusFilter);
|
||||||
}, [currentPage, limit, orderBy, debouncedSearch, tenantId]);
|
}, [currentPage, limit, orderBy, debouncedSearch, statusFilter, tenantId]);
|
||||||
|
|
||||||
const handleCreateRole = async (data: CreateRoleRequest): Promise<void> => {
|
const handleCreateRole = async (data: CreateRoleRequest): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
@ -158,7 +168,7 @@ export const RolesTable = forwardRef<RolesTableRef, RolesTableProps>(
|
|||||||
: `${data.name} has been added`;
|
: `${data.name} has been added`;
|
||||||
showToast.success(message, description);
|
showToast.success(message, description);
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
await fetchRoles(currentPage, limit, orderBy);
|
await fetchRoles(currentPage, limit, orderBy, debouncedSearch, statusFilter);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
throw err;
|
throw err;
|
||||||
} finally {
|
} finally {
|
||||||
@ -197,7 +207,7 @@ export const RolesTable = forwardRef<RolesTableRef, RolesTableProps>(
|
|||||||
setEditModalOpen(false);
|
setEditModalOpen(false);
|
||||||
setSelectedRoleId(null);
|
setSelectedRoleId(null);
|
||||||
// setSelectedRoleName("");
|
// setSelectedRoleName("");
|
||||||
await fetchRoles(currentPage, limit, orderBy);
|
await fetchRoles(currentPage, limit, orderBy, debouncedSearch, statusFilter);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
throw err;
|
throw err;
|
||||||
} finally {
|
} finally {
|
||||||
@ -282,6 +292,15 @@ export const RolesTable = forwardRef<RolesTableRef, RolesTableProps>(
|
|||||||
</StatusBadge>
|
</StatusBadge>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "is_active",
|
||||||
|
label: "Status",
|
||||||
|
render: (role) => (
|
||||||
|
<StatusBadge variant={role.is_active !== false ? "success" : "failure"}>
|
||||||
|
{role.is_active !== false ? "Active" : "Inactive"}
|
||||||
|
</StatusBadge>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "user_count",
|
key: "user_count",
|
||||||
label: "Users",
|
label: "Users",
|
||||||
@ -357,6 +376,14 @@ export const RolesTable = forwardRef<RolesTableRef, RolesTableProps>(
|
|||||||
</StatusBadge>
|
</StatusBadge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-[#9aa6b2]">Status:</span>
|
||||||
|
<div className="mt-1">
|
||||||
|
<StatusBadge variant={role.is_active !== false ? "success" : "failure"}>
|
||||||
|
{role.is_active !== false ? "Active" : "Inactive"}
|
||||||
|
</StatusBadge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-[#9aa6b2]">Users:</span>
|
<span className="text-[#9aa6b2]">Users:</span>
|
||||||
<p className="text-[#0f1724] font-normal mt-1">
|
<p className="text-[#0f1724] font-normal mt-1">
|
||||||
@ -394,6 +421,19 @@ export const RolesTable = forwardRef<RolesTableRef, RolesTableProps>(
|
|||||||
onChange={setSearch}
|
onChange={setSearch}
|
||||||
placeholder="Search..."
|
placeholder="Search..."
|
||||||
/>
|
/>
|
||||||
|
<FilterDropdown
|
||||||
|
label="Status"
|
||||||
|
options={[
|
||||||
|
{ value: "true", label: "Active" },
|
||||||
|
{ value: "false", label: "Inactive" },
|
||||||
|
]}
|
||||||
|
value={statusFilter}
|
||||||
|
onChange={(value) => {
|
||||||
|
setStatusFilter(Array.isArray(value) ? null : value || null);
|
||||||
|
setCurrentPage(1);
|
||||||
|
}}
|
||||||
|
placeholder="All Statuses"
|
||||||
|
/>
|
||||||
{isTenantAdmin && (
|
{isTenantAdmin && (
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
size="default"
|
size="default"
|
||||||
@ -499,6 +539,21 @@ export const RolesTable = forwardRef<RolesTableRef, RolesTableProps>(
|
|||||||
placeholder="Search by name or code..."
|
placeholder="Search by name or code..."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Status Filter */}
|
||||||
|
<FilterDropdown
|
||||||
|
label="Status"
|
||||||
|
options={[
|
||||||
|
{ value: "true", label: "Active" },
|
||||||
|
{ value: "false", label: "Inactive" },
|
||||||
|
]}
|
||||||
|
value={statusFilter}
|
||||||
|
onChange={(value) => {
|
||||||
|
setStatusFilter(Array.isArray(value) ? null : value || null);
|
||||||
|
setCurrentPage(1);
|
||||||
|
}}
|
||||||
|
placeholder="All Statuses"
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Sort Filter */}
|
{/* Sort Filter */}
|
||||||
<FilterDropdown
|
<FilterDropdown
|
||||||
label="Sort by"
|
label="Sort by"
|
||||||
|
|||||||
@ -15,7 +15,8 @@ export const roleService = {
|
|||||||
limit: number = 20,
|
limit: number = 20,
|
||||||
orderBy?: string[] | null,
|
orderBy?: string[] | null,
|
||||||
search?: string | null,
|
search?: string | null,
|
||||||
scope?: string | null
|
scope?: string | null,
|
||||||
|
isActive?: boolean | null
|
||||||
): Promise<RolesResponse> => {
|
): Promise<RolesResponse> => {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.append('page', String(page));
|
params.append('page', String(page));
|
||||||
@ -26,6 +27,9 @@ export const roleService = {
|
|||||||
if (scope) {
|
if (scope) {
|
||||||
params.append('scope', scope);
|
params.append('scope', scope);
|
||||||
}
|
}
|
||||||
|
if (isActive !== undefined && isActive !== null) {
|
||||||
|
params.append('is_active', String(isActive));
|
||||||
|
}
|
||||||
if (orderBy && Array.isArray(orderBy) && orderBy.length === 2) {
|
if (orderBy && Array.isArray(orderBy) && orderBy.length === 2) {
|
||||||
params.append('orderBy[]', orderBy[0]);
|
params.append('orderBy[]', orderBy[0]);
|
||||||
params.append('orderBy[]', orderBy[1]);
|
params.append('orderBy[]', orderBy[1]);
|
||||||
@ -38,7 +42,8 @@ export const roleService = {
|
|||||||
page: number = 1,
|
page: number = 1,
|
||||||
limit: number = 20,
|
limit: number = 20,
|
||||||
orderBy?: string[] | null,
|
orderBy?: string[] | null,
|
||||||
search?: string | null
|
search?: string | null,
|
||||||
|
isActive?: boolean | null
|
||||||
): Promise<RolesResponse> => {
|
): Promise<RolesResponse> => {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.append('page', String(page));
|
params.append('page', String(page));
|
||||||
@ -47,6 +52,9 @@ export const roleService = {
|
|||||||
if (search) {
|
if (search) {
|
||||||
params.append('search', search);
|
params.append('search', search);
|
||||||
}
|
}
|
||||||
|
if (isActive !== undefined && isActive !== null) {
|
||||||
|
params.append('is_active', String(isActive));
|
||||||
|
}
|
||||||
if (orderBy && Array.isArray(orderBy) && orderBy.length === 2) {
|
if (orderBy && Array.isArray(orderBy) && orderBy.length === 2) {
|
||||||
params.append('orderBy[]', orderBy[0]);
|
params.append('orderBy[]', orderBy[0]);
|
||||||
params.append('orderBy[]', orderBy[1]);
|
params.append('orderBy[]', orderBy[1]);
|
||||||
|
|||||||
@ -5,6 +5,7 @@ export interface Role {
|
|||||||
description?: string;
|
description?: string;
|
||||||
scope: 'platform' | 'tenant' | 'module';
|
scope: 'platform' | 'tenant' | 'module';
|
||||||
is_system?: boolean;
|
is_system?: boolean;
|
||||||
|
is_active?: boolean;
|
||||||
tenant_id?: string | null;
|
tenant_id?: string | null;
|
||||||
module_ids?: string[] | null;
|
module_ids?: string[] | null;
|
||||||
modules?: string[] | null;
|
modules?: string[] | null;
|
||||||
@ -37,6 +38,7 @@ export interface CreateRoleRequest {
|
|||||||
name: string;
|
name: string;
|
||||||
code: string;
|
code: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
is_active?: boolean;
|
||||||
tenant_id?: string | null;
|
tenant_id?: string | null;
|
||||||
module_ids?: string[] | null;
|
module_ids?: string[] | null;
|
||||||
modules?: string[] | null;
|
modules?: string[] | null;
|
||||||
@ -58,6 +60,7 @@ export interface UpdateRoleRequest {
|
|||||||
name?: string;
|
name?: string;
|
||||||
code?: string;
|
code?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
is_active?: boolean;
|
||||||
tenant_id?: string | null;
|
tenant_id?: string | null;
|
||||||
module_ids?: string[] | null;
|
module_ids?: string[] | null;
|
||||||
modules?: string[] | null;
|
modules?: string[] | null;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user