feat: add isActive status support to roles with filtering and management UI

This commit is contained in:
Yashwin 2026-06-22 15:42:20 +05:30
parent f480979020
commit b0fe466a3d
10 changed files with 210 additions and 24 deletions

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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,

View File

@ -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>

View File

@ -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={[

View File

@ -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);

View File

@ -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"

View File

@ -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]);

View File

@ -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;