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:
@@ -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;
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user