feat(admins): add filter functionality to admins list

This commit introduces filtering capabilities to the administrators table, allowing users to refine the list based on specific criteria.

A new `ListHeader` component has been implemented, which now contains the "Create Admin" button and a new "Filter" button. Clicking the filter button opens a modal containing the `AdminListFilters` form. The state for managing the filter modal's visibility is handled within the `useAdminsPresenter` hook. A new `FilterIcon` has also been added to support this feature.
This commit is contained in:
AmirReza Jamali
2025-10-12 15:20:52 +03:30
parent 1f17a1c601
commit 5b73389d64
11 changed files with 191 additions and 26 deletions
@@ -0,0 +1,59 @@
import InputGroup from "@/components/FormElements/InputGroup";
import { Select } from "@/components/FormElements/select";
import { AdminRoles, NotificationChannels } from "@/constants/enums";
import { getEnumAsArray } from "@/utils/shared";
import useAdminListFilterPresenter from "./useAdminListFilterPresenter";
interface IProps {}
const AdminListFilters = ({}: IProps) => {
const { register } = useAdminListFilterPresenter();
return (
<form className="grid !min-w-full grid-cols-3 gap-4">
<Select
{...register("method")}
items={getEnumAsArray(NotificationChannels)}
label="Method"
name="method"
placeholder="Method"
/>
<Select
{...register("status")}
items={getEnumAsArray(NotificationChannels)}
label="status"
name="status"
placeholder="Status"
/>
<InputGroup
{...register("email")}
name="email"
label="email"
placeholder="email"
type="email"
/>
<Select
{...register("role")}
name="role"
items={getEnumAsArray(AdminRoles)}
label="Role"
placeholder="Role"
/>
<InputGroup
{...register("username")}
label="username"
name="username"
placeholder="username"
type="text"
/>
<InputGroup
{...register("full_name")}
label="full name"
name="full_name"
placeholder="full name"
type="text"
/>
</form>
);
};
export default AdminListFilters;
+17 -6
View File
@@ -2,8 +2,8 @@
import { PencilSquareIcon, TrashIcon, UserSettingIcon } from "@/assets/icons";
import ConfirmationModal from "@/components/ui/ConfirmationModal";
import ListHeader from "@/components/ui/ListHeader";
import Modal from "@/components/ui/modal";
import { _TooltipDefaultParams } from "@/constants/tooltip";
import Status from "@/components/ui/Status";
import {
Table,
@@ -15,8 +15,9 @@ import {
} from "@/components/ui/table";
import TableSkeleton from "@/components/ui/TableSkeleton";
import { AdminStatus } from "@/constants/enums";
import Link from "next/link";
import { _TooltipDefaultParams } from "@/constants/tooltip";
import { Tooltip } from "react-tooltip";
import AdminListFilters from "./AdminListFilters";
import ChangeStatusModalContent from "./ChangeStatusModalContent";
import { useAdminsPresenter } from "./useAdminsPresenter";
@@ -32,7 +33,8 @@ const AdminsTable = () => {
setIsChangeStatusModalOpen,
columns,
setCurrentUser,
pathName,
isFilterModalOpen,
setIsFilterModalOpen,
changeStatus,
currentUser,
setStatus,
@@ -41,9 +43,10 @@ const AdminsTable = () => {
return (
<div className="flex flex-col rounded-[10px] bg-white px-7.5 pb-4 pt-7.5 shadow-1 dark:bg-gray-dark dark:shadow-card">
<button className="mb-5 flex w-fit justify-center self-end rounded-lg bg-primary p-[13px] font-medium text-white hover:bg-opacity-90">
<Link href={`${pathName}/create`}>Create Admin</Link>
</button>
<ListHeader
onFilter={() => setIsFilterModalOpen(true)}
createButtonText="Create Admin"
/>
<Table>
<TableHeader>
<TableRow className="border-none uppercase [&>th]:text-start">
@@ -143,6 +146,14 @@ const AdminsTable = () => {
setStatus={setStatus}
/>
</Modal>
<Modal
isOpen={isFilterModalOpen}
onClose={() => setIsFilterModalOpen(false)}
classNames="w-full"
>
<AdminListFilters />
</Modal>
{isModalOpen && (
<ConfirmationModal
isOpen={isModalOpen}
@@ -0,0 +1,10 @@
import { useForm } from "react-hook-form";
const useAdminListFilterPresenter = () => {
const { register } = useForm();
return {
register,
};
};
export default useAdminListFilterPresenter;
@@ -14,6 +14,7 @@ import { useForm } from "react-hook-form";
export const useAdminsPresenter = () => {
const [isChangeStatusModalOpen, setIsChangeStatusModalOpen] = useState(false);
const [isFilterModalOpen, setIsFilterModalOpen] = useState(false);
const [currentUser, setCurrentUser] = useState<IUser | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [status, setStatus] = useState<string>(
@@ -79,5 +80,7 @@ export const useAdminsPresenter = () => {
register,
errors,
handleSubmit,
isFilterModalOpen,
setIsFilterModalOpen,
};
};
+21 -19
View File
@@ -154,25 +154,27 @@ const CustomersTable = () => {
))}
</TableBody>
</Table>
<Modal
onConfirm={() => {
if (!selectedCompanies || !currentCustomer) {
toast.error("Invalid credentials");
return;
}
assignSelectedCompanies({
credentials: selectedCompanies,
id: currentCustomer?.id ?? "",
});
}}
isOpen={isAssignCompanyModalOpen}
onClose={() => setIsAssignCompanyModalOpen(false)}
>
<AssignToCompanyModalContent
setCompanies={setSelectedCompanies}
defaultValues={selectedCompanies}
/>
</Modal>
<div className="w-1/12">
<Modal
onConfirm={() => {
if (!selectedCompanies || !currentCustomer) {
toast.error("Invalid credentials");
return;
}
assignSelectedCompanies({
credentials: selectedCompanies,
id: currentCustomer?.id ?? "",
});
}}
isOpen={isAssignCompanyModalOpen}
onClose={() => setIsAssignCompanyModalOpen(false)}
>
<AssignToCompanyModalContent
setCompanies={setSelectedCompanies}
defaultValues={selectedCompanies}
/>
</Modal>
</div>
{isModalOpen && (
<ConfirmationModal
isOpen={isModalOpen}
@@ -66,6 +66,7 @@ const NotificationHistoryTable = () => {
unix: item.created_at,
})}
</TableCell>
<TableCell colSpan={100}>{item.recipient?.full_name}</TableCell>
<TableCell colSpan={100} className="truncate">
{truncateString(item.message, 20, 20)}
</TableCell>
@@ -15,6 +15,7 @@ const useNotificationHistoryTablePresenter = () => {
"title",
"channel",
"created at",
"receiver",
"message",
"priority",
"type",
+45
View File
@@ -0,0 +1,45 @@
"use client";
import { FilterIcon } from "@/assets/icons";
import Link from "next/link";
import { usePathname } from "next/navigation";
import IsVisible from "./IsVisible";
interface IProps {
hasCreate?: boolean;
hasFilter?: boolean;
createButtonText?: string;
filterButtonText?: string;
onFilter?: () => void;
}
const ListHeader = ({
hasCreate = true,
hasFilter = true,
createButtonText = "Create",
filterButtonText = "Filter",
onFilter,
}: IProps) => {
const pathName = usePathname();
return (
<div className="flex items-center justify-between">
<IsVisible condition={hasFilter}>
<button
onClick={onFilter}
className="mb-5 flex w-fit justify-center self-end rounded-2xl border bg-gray-5 bg-opacity-20 px-6 py-2 font-medium transition duration-300 hover:bg-opacity-30 dark:border-gray-7 dark:bg-black dark:bg-opacity-40 dark:text-white dark:hover:bg-opacity-90"
>
<span>
<FilterIcon />
</span>
{filterButtonText}
</button>
</IsVisible>
<IsVisible condition={hasCreate}>
<button className="mb-5 flex w-fit justify-center self-end rounded-lg bg-primary p-[13px] font-medium text-white hover:bg-opacity-90">
<Link href={`${pathName}/create`}>{createButtonText}</Link>
</button>
</IsVisible>
</div>
);
};
export default ListHeader;
+3 -1
View File
@@ -12,6 +12,7 @@ interface ModalProps {
confirmText?: string;
cancelText?: string;
showButtons?: boolean;
classNames?: string;
}
const Modal: React.FC<ModalProps> = ({
@@ -22,6 +23,7 @@ const Modal: React.FC<ModalProps> = ({
confirmText = "confirm",
cancelText = "close",
showButtons = true,
classNames,
}) => {
const [mounted, setMounted] = useState(false);
const isMutating = useIsMutating();
@@ -49,7 +51,7 @@ const Modal: React.FC<ModalProps> = ({
onClick={onClose}
>
<div
className="max-h-[90vh] min-w-75 max-w-[90vw] overflow-auto rounded-lg bg-white p-5 shadow-4 dark:bg-dark-2"
className={`max-h-[90vh] max-w-[90vw] overflow-auto rounded-lg bg-white p-5 shadow-4 dark:bg-dark-2 ${classNames}`}
onClick={(e) => e.stopPropagation()}
>
<button