diff --git a/src/assets/icons.tsx b/src/assets/icons.tsx index eed4ddc..7c3406b 100644 --- a/src/assets/icons.tsx +++ b/src/assets/icons.tsx @@ -215,6 +215,20 @@ export function GlobeIcon(props: IconProps) { ); } +export function UserSettingIcon(props: IconProps) { + return ( + + + + ); +} export function TrashIcon(props: IconProps) { return ( >; +} +const ChangeStatusModalContent: FC = ({ status, setStatus }) => { + const { register } = useForm({ + mode: "onChange", + defaultValues: { status }, + }); + const adminStatusEntries = Object.entries(AdminStatus).map( + ([key, value]) => ({ + label: capitalize(key), + value, + }), + ); + + return ( +
+ +
+ ); +}; + +export default ChangeStatusModalContent; diff --git a/src/components/Tables/admins/index.tsx b/src/components/Tables/admins/index.tsx index 1442972..92720a5 100644 --- a/src/components/Tables/admins/index.tsx +++ b/src/components/Tables/admins/index.tsx @@ -1,7 +1,9 @@ "use client"; -import { PencilSquareIcon, TrashIcon } from "@/assets/icons"; +import { PencilSquareIcon, TrashIcon, UserSettingIcon } from "@/assets/icons"; import ConfirmationModal from "@/components/ui/ConfirmationModal"; +import Modal from "@/components/ui/modal"; +import Status from "@/components/ui/Status"; import { Table, TableBody, @@ -11,7 +13,9 @@ import { TableRow, } from "@/components/ui/table"; import TableSkeleton from "@/components/ui/TableSkeleton"; +import { AdminStatus } from "@/constants/enums"; import Link from "next/link"; +import ChangeStatusModalContent from "./ChangeStatusModalContent"; import { useAdminsPresenter } from "./useAdminsPresenter"; const AdminsTable = () => { @@ -22,9 +26,15 @@ const AdminsTable = () => { onConfirmDelete, isModalOpen, setIsModalOpen, - modalConfig, + isChangeStatusModalOpen, + setIsChangeStatusModalOpen, + columns, setCurrentUser, pathName, + changeStatus, + currentUser, + setStatus, + status, } = useAdminsPresenter(); return ( @@ -35,31 +45,24 @@ const AdminsTable = () => {
- - Full name - - - Email - - - Role - - - Username - - - Actions - + {columns.map((column) => ( + + {column} + + ))} - {isPending && } + {isPending && } - {allUsers.map((item) => { + {allUsers.map((item, index) => { return ( + + {index + 1} + {item.full_name} @@ -72,6 +75,9 @@ const AdminsTable = () => { {item.username} + + {item.status} +
@@ -85,6 +91,16 @@ const AdminsTable = () => { Delete Admin +
+ setIsChangeStatusModalOpen(false)} + onConfirm={() => { + changeStatus({ id: currentUser?.id!, credentials: { status } }); + }} + > + + {isModalOpen && ( { onConfirmDelete(); }} diff --git a/src/components/Tables/admins/useAdminsPresenter.ts b/src/components/Tables/admins/useAdminsPresenter.ts index 0f52de6..8f63241 100644 --- a/src/components/Tables/admins/useAdminsPresenter.ts +++ b/src/components/Tables/admins/useAdminsPresenter.ts @@ -1,84 +1,83 @@ "use client"; -import { useAdminListInfiniteQuery, useDeleteAdmin } from "@/hooks/queries"; -import useDebounce from "@/hooks/useDebounce"; -import { useState, useEffect, useRef } from "react"; -import { z } from "zod"; -import { useInView } from "react-intersection-observer"; -import { IUser } from "@/lib/api"; -import { ConfirmationModalProps } from "@/components/ui/ConfirmationModal"; +import { apiDefaultParams } from "@/constants/Api_Params"; +import { AdminStatus } from "@/constants/enums"; +import { + useAdminListInfiniteQuery, + useChangeAdminStatus, + useDeleteAdmin, +} from "@/hooks/queries"; +import { IUser, TChangeAdminStatusCredentials } from "@/lib/api"; import { usePathname, useRouter } from "next/navigation"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; + export const useAdminsPresenter = () => { + const [isChangeStatusModalOpen, setIsChangeStatusModalOpen] = useState(false); const [currentUser, setCurrentUser] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); + const [status, setStatus] = useState( + currentUser?.status ?? AdminStatus.ACTIVE, + ); const router = useRouter(); - const [modalConfig, setModalConfig] = useState< - Partial - >({}); - - const [search, setSearch] = useState(""); - - const [params, setParams] = useState>({ - sort: "created_at", - }); - const debouncedSearch = useDebounce(search, 1000); - const pathName = usePathname(); const { - data, - error, - isPending, - fetchNextPage, - hasNextPage, - isFetchingNextPage, - } = useAdminListInfiniteQuery(params); + register, + formState: { errors }, + handleSubmit, + } = useForm({ + mode: "onChange", + defaultValues: { + status: currentUser?.status, + }, + }); + const [params, setParams] = useState>({ + ...apiDefaultParams, + }); + const pathName = usePathname(); + const { data, isPending } = useAdminListInfiniteQuery(params); const { mutate: deleteAdmin } = useDeleteAdmin(() => { setIsModalOpen(false); }); - const { inView } = useInView({ threshold: 0.5 }); - - useEffect(() => { - if (inView && hasNextPage && data && data.pages.length < 5) { - fetchNextPage(); - } - }, [inView, hasNextPage, data, fetchNextPage]); - - useEffect(() => { - const result = z.string().safeParse(debouncedSearch); - if (result.success) { - setParams((prevParams) => ({ - ...prevParams, - })); - } - }, [debouncedSearch]); - - const handleFilterChange = (e: React.ChangeEvent) => { - setSearch(e.target.value); - }; const onConfirmDelete = () => { if (currentUser) { deleteAdmin(currentUser.id); } }; + const { mutate: changeStatus } = useChangeAdminStatus({ + id: currentUser?.id ?? "", + successCallback: () => { + setIsChangeStatusModalOpen(false); + }, + }); const allUsers = data?.pages.flatMap((page) => page.data.users) ?? []; + const columns = [ + "row", + "full name", + "email", + "role", + "username", + "status", + "actions", + ]; return { allUsers, - currentAdmin: currentUser, - error, - handleFilterChange, - isFetchingNextPage, - fetchNextPage, + columns, isPending, onConfirmDelete, isModalOpen, setIsModalOpen, - modalConfig, - setModalConfig, - search, - hasNextPage, - data, router, setCurrentUser, pathName, + status, + setStatus, + changeStatus, + isChangeStatusModalOpen, + currentUser, + setIsChangeStatusModalOpen, + register, + errors, + handleSubmit, }; }; diff --git a/src/components/Tables/companies/index.tsx b/src/components/Tables/companies/index.tsx index f94cda6..f3c11de 100644 --- a/src/components/Tables/companies/index.tsx +++ b/src/components/Tables/companies/index.tsx @@ -58,6 +58,9 @@ const CompaniesTable = ({}: IProps) => { key={company.id} className="odd:bg-gray-2 dark:odd:bg-gray-7" > + + {index + 1} + {company.name} diff --git a/src/components/Tables/companies/useCompanyListPresenter.ts b/src/components/Tables/companies/useCompanyListPresenter.ts index 3f82769..3be8313 100644 --- a/src/components/Tables/companies/useCompanyListPresenter.ts +++ b/src/components/Tables/companies/useCompanyListPresenter.ts @@ -6,8 +6,7 @@ import { } from "@/hooks/queries"; import useDebounce from "@/hooks/useDebounce"; import { TCompany } from "@/lib/api"; -import { usePathname } from "next/navigation"; -import { useRouter } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import { ChangeEvent, useEffect, useRef, useState } from "react"; import { useInView } from "react-intersection-observer"; import { z } from "zod"; @@ -26,6 +25,7 @@ export const useCompanyListPresenter = () => { const [isModalOpen, setIsModalOpen] = useState(false); const router = useRouter(); const columns = [ + "row", "name", "email", "phone", diff --git a/src/components/Tables/customers/index.tsx b/src/components/Tables/customers/index.tsx index a02da66..79c303f 100644 --- a/src/components/Tables/customers/index.tsx +++ b/src/components/Tables/customers/index.tsx @@ -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 = () => { {isPending && } - {allCustomers.map((customer) => ( + {allCustomers.map((customer, index) => ( + {index + 1} {customer.full_name} {customer.email} @@ -71,7 +72,7 @@ const CustomersTable = () => { {customer.type} - {customer.status} + {customer.status} {customer.companies?.length ? ( @@ -82,7 +83,9 @@ const CustomersTable = () => { None )} - {customer.role ?? "-"} + + {customer.role ?? "-"} +
@@ -139,12 +142,12 @@ const CustomersTable = () => { {isModalOpen && ( { onConfirmDelete(); }} diff --git a/src/components/Tables/customers/useCustomerListPresenter.ts b/src/components/Tables/customers/useCustomerListPresenter.ts index 4a5facd..9cc641c 100644 --- a/src/components/Tables/customers/useCustomerListPresenter.ts +++ b/src/components/Tables/customers/useCustomerListPresenter.ts @@ -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(null); const [isAssignCompanyModalOpen, setIsAssignCompanyModalOpen] = useState(false); - const { inView } = useInView({ threshold: 0.5 }); const [selectedCompanies, setSelectedCompanies] = useState(null); const [currentCustomer, setCurrentCustomer] = useState( null, ); - const [modalConfig, setModalConfig] = useState< - Partial - >({}); + const [isModalOpen, setIsModalOpen] = useState(false); const pathName = usePathname(); - const [search, setSearch] = useState(""); const [params, setParams] = useState>({ 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) => { - 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, }; diff --git a/src/components/Tables/tenders/index.tsx b/src/components/Tables/tenders/index.tsx index aa43cff..ebd76b4 100644 --- a/src/components/Tables/tenders/index.tsx +++ b/src/components/Tables/tenders/index.tsx @@ -17,7 +17,7 @@ import TableSkeleton from "@/components/ui/TableSkeleton"; import { cn } from "@/lib/utils"; import Link from "next/link"; const TendersTable = () => { - const { allTenders, isPending, router, pathName, setParams } = + const { allTenders, isPending, router, pathName, setParams, columns } = useTenderListPresenter(); return ( @@ -25,29 +25,15 @@ const TendersTable = () => { - - Row - - - Title - - - Country Code - - - Submission Url - - - Status - - - - Actions - + {columns.map((column) => ( + + {column} + + ))} - {isPending && } + {isPending && } {allTenders?.data.tenders?.map((item, index) => !item ? ( diff --git a/src/components/Tables/tenders/useTenderListPresenter.ts b/src/components/Tables/tenders/useTenderListPresenter.ts index 9c43feb..41f3b47 100644 --- a/src/components/Tables/tenders/useTenderListPresenter.ts +++ b/src/components/Tables/tenders/useTenderListPresenter.ts @@ -34,9 +34,17 @@ const useTenderListPresenter = () => { const handleFilterChange = (e: React.ChangeEvent) => { setSearch(e.target.value); }; - + const columns = [ + "row", + "title", + "country code", + "submission url", + "status", + "actions", + ]; return { currentTender, + columns, setCurrentTender, search, handleFilterChange, diff --git a/src/components/ui/Status.tsx b/src/components/ui/Status.tsx index 24e00d1..4684e37 100644 --- a/src/components/ui/Status.tsx +++ b/src/components/ui/Status.tsx @@ -26,7 +26,7 @@ const Status = ({ status, children }: IProps) => { const blues = ["awarded", "processing", "in-review", "scheduled"]; const yellows = ["pending", "warning", "on-hold", "delayed"]; const oranges = ["expired", "expiring", "limited"]; - const grays = ["draft", "disabled", "archived", "paused"]; + const grays = ["draft", "disabled", "archived", "paused", "suspended"]; return ( = ({ onConfirm, }) => { const [mounted, setMounted] = useState(false); - + const isMutating = useIsMutating(); useEffect(() => { setMounted(true); }, []); @@ -57,7 +58,11 @@ const Modal: React.FC = ({ className="mt-6 flex w-fit justify-center rounded-lg bg-primary p-[13px] font-medium text-white hover:bg-opacity-90" onClick={onConfirm} > - Confirm + {isMutating ? ( +
+ ) : ( + "Confirm" + )}