feat(admins): implement admin status management

This commit introduces the functionality for super admins to change the status of other admin users to either 'Active' or 'Inactive'. This provides better control over admin account access.

Key changes include:
- A 'Status' column with a visual badge has been added to the admins table.
- A new 'Change Status' action button, using a new `UserSettingIcon`, opens a modal for status updates.
- The backend service and frontend mutation for updating an admin's status are implemented.
- The `IUser` type and a new `AdminStatus` enum have been added to support this feature.

Additionally, the confirmation modal implementation has been simplified across the admin and customer tables by removing the `modalConfig` state.
This commit is contained in:
AmirReza Jamali
2025-09-21 13:18:53 +03:30
parent e00e75b8be
commit 1d78b2b65d
18 changed files with 274 additions and 173 deletions
+15 -12
View File
@@ -1,5 +1,5 @@
"use client";
import { PencilSquareIcon, TrashIcon, UserIcon } from "@/assets/icons";
import { PencilSquareIcon, TrashIcon } from "@/assets/icons";
import ConfirmationModal from "@/components/ui/ConfirmationModal";
import {
Table,
@@ -12,6 +12,7 @@ import {
import Link from "next/link";
import useCustomerListPresenter from "./useCustomerListPresenter";
import { Building } from "@/components/Layouts/sidebar/icons";
import Modal from "@/components/ui/modal";
import Status from "@/components/ui/Status";
import TableSkeleton from "@/components/ui/TableSkeleton";
@@ -26,7 +27,6 @@ const CustomersTable = () => {
router,
columns,
allCustomers,
modalConfig,
isModalOpen,
assignSelectedCompanies,
selectedCompanies,
@@ -59,8 +59,9 @@ const CustomersTable = () => {
</TableHeader>
{isPending && <TableSkeleton column={columns.length} />}
<TableBody>
{allCustomers.map((customer) => (
{allCustomers.map((customer, index) => (
<TableRow key={customer.id}>
<TableCell colSpan={100}>{index + 1}</TableCell>
<TableCell colSpan={100}>{customer.full_name}</TableCell>
<TableCell colSpan={100}>{customer.email}</TableCell>
<TableCell colSpan={100}>
@@ -71,7 +72,7 @@ const CustomersTable = () => {
{customer.type}
</TableCell>
<TableCell colSpan={100}>
<Status status={customer.status}>{customer.status}</Status>
<Status status={customer.status}>{customer.status}</Status>
</TableCell>
<TableCell colSpan={100}>
{customer.companies?.length ? (
@@ -82,7 +83,9 @@ const CustomersTable = () => {
<span>None</span>
)}
</TableCell>
<TableCell colSpan={100}>{customer.role ?? "-"}</TableCell>
<TableCell colSpan={100} className="uppercase">
{customer.role ?? "-"}
</TableCell>
<TableCell className="text-start xl:pr-7.5">
<div className="flex items-center justify-start gap-x-3.5">
<button
@@ -112,7 +115,7 @@ const CustomersTable = () => {
}}
>
<span className="sr-only">Assign To Company </span>
<UserIcon />
<Building />
</button>
</div>
</TableCell>
@@ -139,12 +142,12 @@ const CustomersTable = () => {
{isModalOpen && (
<ConfirmationModal
isOpen={isModalOpen}
title={modalConfig.title || "Confirm"}
message={modalConfig.message || "Are you sure?"}
variant={modalConfig.variant || "default"}
size={modalConfig.size || "md"}
confirmText={modalConfig.confirmText ?? "Confirm"}
cancelText={modalConfig.cancelText ?? "Cancel"}
title={"Confirm"}
message={"Are you sure?"}
variant={"default"}
size={"md"}
confirmText={"Confirm"}
cancelText={"Cancel"}
onConfirm={() => {
onConfirmDelete();
}}
@@ -1,53 +1,30 @@
"use client";
import { ConfirmationModalProps } from "@/components/ui/ConfirmationModal";
import {
useAssignCompany,
useCustomersInfiniteQuery,
useDeleteCustomerQuery,
} from "@/hooks/queries";
import useDebounce from "@/hooks/useDebounce";
import { TAssignCustomerToCompanyCredentials, TCustomer } from "@/lib/api";
import { usePathname, useRouter } from "next/navigation";
import { ChangeEvent, useEffect, useRef, useState } from "react";
import { useInView } from "react-intersection-observer";
import { z } from "zod";
import { useState } from "react";
const useCustomerListPresenter = () => {
const deleteModal = useRef<HTMLDialogElement | null>(null);
const [isAssignCompanyModalOpen, setIsAssignCompanyModalOpen] =
useState(false);
const { inView } = useInView({ threshold: 0.5 });
const [selectedCompanies, setSelectedCompanies] =
useState<TAssignCustomerToCompanyCredentials | null>(null);
const [currentCustomer, setCurrentCustomer] = useState<TCustomer | null>(
null,
);
const [modalConfig, setModalConfig] = useState<
Partial<ConfirmationModalProps>
>({});
const [isModalOpen, setIsModalOpen] = useState(false);
const pathName = usePathname();
const [search, setSearch] = useState("");
const [params, setParams] = useState<Record<string, any>>({
sort: "created_at",
});
const router = useRouter();
const debouncedSearch = useDebounce(search, 1000);
const {
data,
error,
isPending,
isError,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useCustomersInfiniteQuery(params);
useEffect(() => {
if (inView && hasNextPage && data && data.pages.length < 5) {
fetchNextPage();
}
}, [inView, hasNextPage, data, fetchNextPage]);
const { data, isPending } = useCustomersInfiniteQuery(params);
const { mutate: deleteCustomer } = useDeleteCustomerQuery(() =>
setIsModalOpen(false),
);
@@ -57,18 +34,8 @@ const useCustomerListPresenter = () => {
setIsAssignCompanyModalOpen(false);
},
);
useEffect(() => {
const result = z.string().safeParse(debouncedSearch);
if (result.success) {
setParams((prevParams) => ({
...prevParams,
}));
}
}, [debouncedSearch]);
const handleFilterChange = (e: ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value);
};
const columns = [
"row",
"full name",
"email",
"created at",
@@ -85,20 +52,10 @@ const useCustomerListPresenter = () => {
};
const customers = data?.pages.flatMap((page) => page.data.customers) ?? [];
return {
hasNextPage,
fetchNextPage,
allCustomers: customers,
currentCustomer,
deleteCustomer,
deleteModal,
error,
handleFilterChange,
isError,
isFetchingNextPage,
columns,
data,
isPending,
search,
setCurrentCustomer,
selectedCompanies,
setSelectedCompanies,
@@ -108,8 +65,6 @@ const useCustomerListPresenter = () => {
assignSelectedCompanies,
pathName,
router,
modalConfig,
setModalConfig,
isModalOpen,
setIsModalOpen,
};