feat: rebuild the admin pages

This commit is contained in:
Nathnael
2026-07-23 07:40:48 +00:00
parent 919c3a84ff
commit cbff529e31
16 changed files with 1485 additions and 579 deletions

View File

@@ -0,0 +1,199 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { useTranslation } from "react-i18next";
import { useErrorHandler } from "@/shared/hooks/useErrorHandler";
import {
assignOrganizationAdmin,
fetchAllUnitAdminById,
OrganizationAdminPayload,
} from "@/super-admin/services/api/organizationAdminService";
import {
assignOrgAdminRole,
removeOrgAdminRole,
removeUnitAdminRole,
} from "@/super-admin/services/api/userRoleService";
import {
activateUser,
deactivateUser,
} from "@/super-admin/services/api/userService";
import { resendVerificationCode } from "@/shared/services/authService";
import {
updateProfile,
UpdateProfilePayload,
} from "@/user-management/services/api/employeePositionsService";
export interface OrgAdminUserRole {
id: string;
organizationId?: string | null;
unitId?: string | null;
role?: { key?: string } | null;
}
/** User row returned by GET /organizations/all-admins/:id (userRoles.role relation included). */
export interface OrgAdminUser {
id: string;
name?: { am?: string; en?: string };
username?: string;
email?: string;
phoneNumber?: string;
isActive: boolean;
hasSetPassword: boolean;
status?: string;
createdAt?: string;
userRoles?: OrgAdminUserRole[];
}
export const ORG_ADMINS_KEY = "orgAdmins";
export const ORG_PICKER_KEY = "orgAdminsOrgPicker";
export interface RemoveAdminInput {
userId: string;
/** set for org-admin removal */
organizationId?: string;
/** set for unit-admin removal (wins over organizationId) */
unitId?: string;
}
export const useOrgAdmins = (
orgId?: string,
params?: { take?: number; skip?: number },
) => {
const queryClient = useQueryClient();
const { t } = useTranslation();
const { handleError } = useErrorHandler(t);
const {
data: adminsResponse,
isLoading,
isError,
refetch,
} = useQuery({
queryKey: [ORG_ADMINS_KEY, orgId, params],
queryFn: async () => {
const { data } = await fetchAllUnitAdminById(orgId as string, params);
return {
count: (data?.count ?? 0) as number,
items: (data?.items ?? []) as OrgAdminUser[],
};
},
enabled: !!orgId,
staleTime: 5 * 60 * 1000,
retry: false,
});
const invalidate = () => {
queryClient.invalidateQueries({ queryKey: [ORG_ADMINS_KEY] });
// picker + org tables show adminsCount — keep them fresh
queryClient.invalidateQueries({ queryKey: [ORG_PICKER_KEY] });
queryClient.invalidateQueries({ queryKey: ["organizations"] });
queryClient.invalidateQueries({ queryKey: ["organizationAdmins"] });
};
const { mutate: inviteAdmin, isPending: isInviting } = useMutation({
mutationFn: async (payload: OrganizationAdminPayload) => {
const { data } = await assignOrganizationAdmin(payload);
return data;
},
onSuccess: () => {
toast.success(t("orgAdmins.toasts.invited"));
invalidate();
},
onError: handleError,
});
const { mutate: assignAdmin, isPending: isAssigning } = useMutation({
mutationFn: async (payload: { organizationId: string; userId: string }) => {
const { data } = await assignOrgAdminRole(payload);
return data;
},
onSuccess: () => {
toast.success(t("orgAdmins.toasts.assigned"));
invalidate();
},
onError: handleError,
});
const { mutate: removeAdmin, isPending: isRemoving } = useMutation({
mutationFn: async ({ userId, organizationId, unitId }: RemoveAdminInput) => {
const { data } = unitId
? await removeUnitAdminRole({ unitId, userId })
: await removeOrgAdminRole({
organizationId: organizationId as string,
userId,
});
return data;
},
onSuccess: () => {
toast.success(t("orgAdmins.toasts.removed"));
invalidate();
},
onError: handleError,
});
const { mutate: resendInvite, isPending: isResending } = useMutation({
mutationFn: async (payload: { email: string; phoneNumber: string }) => {
const { data } = await resendVerificationCode(payload);
return data;
},
onSuccess: () => {
toast.success(t("orgAdmins.toasts.resent"));
},
onError: handleError,
});
const { mutate: toggleActive, isPending: isToggling } = useMutation({
mutationFn: async ({ id, activate }: { id: string; activate: boolean }) => {
const { data } = activate
? await activateUser(id)
: await deactivateUser(id);
return data;
},
onSuccess: (_data, variables) => {
toast.success(
variables.activate
? t("orgAdmins.toasts.activated")
: t("orgAdmins.toasts.deactivated"),
);
invalidate();
},
onError: handleError,
});
const { mutate: updateAdminProfile, isPending: isUpdatingProfile } =
useMutation({
mutationFn: async ({
id,
payload,
}: {
id: string;
payload: UpdateProfilePayload;
}) => {
const { data } = await updateProfile(payload, id);
return data;
},
onSuccess: () => {
toast.success(t("orgAdmins.toasts.profileUpdated"));
invalidate();
},
onError: handleError,
});
return {
adminsResponse,
isLoading,
isError,
refetch,
inviteAdmin,
isInviting,
assignAdmin,
isAssigning,
removeAdmin,
isRemoving,
resendInvite,
isResending,
toggleActive,
isToggling,
updateAdminProfile,
isUpdatingProfile,
};
};