diff --git a/src/app/inquiries/page.tsx b/src/app/inquiries/page.tsx new file mode 100644 index 0000000..0487d36 --- /dev/null +++ b/src/app/inquiries/page.tsx @@ -0,0 +1,24 @@ +import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb"; +import InquiriesTable from "@/components/Tables/inquiries"; +import { BreadcrumbItem } from "@/types/shared"; + +const InquiriesPage = () => { + const breadcrumbItems: BreadcrumbItem[] = [ + { + href: "/", + name: "Dashboard", + }, + { + href: "/inquiries", + name: "Inquiries", + }, + ]; + return ( + <> + + + + ); +}; + +export default InquiriesPage; diff --git a/src/components/Layouts/sidebar/data/index.ts b/src/components/Layouts/sidebar/data/index.ts index 1f7c361..95762ef 100644 --- a/src/components/Layouts/sidebar/data/index.ts +++ b/src/components/Layouts/sidebar/data/index.ts @@ -86,6 +86,12 @@ export const NAV_DATA: NavData = [ icon: GlobeSearch, items: [], }, + { + title: "Inquiries", + url: "/inquiries", + icon: Icons.BriefcaseTimer, + items: [], + }, ], }, ]; diff --git a/src/components/Layouts/sidebar/icons.tsx b/src/components/Layouts/sidebar/icons.tsx index 8ba9248..1c9b50d 100644 --- a/src/components/Layouts/sidebar/icons.tsx +++ b/src/components/Layouts/sidebar/icons.tsx @@ -240,3 +240,64 @@ export function ArrowLeftIcon(props: PropsType) { ); } +export function BriefcaseTimer(props: PropsType) { + return ( + + + + + + + + + ); +} diff --git a/src/components/Tables/cms/index.tsx b/src/components/Tables/cms/index.tsx index c63374a..7106f6c 100644 --- a/src/components/Tables/cms/index.tsx +++ b/src/components/Tables/cms/index.tsx @@ -3,15 +3,15 @@ import { PencilSquareIcon, TrashIcon } from "@/assets/icons"; import EmptyListWrapper from "@/components/HOC/EmptyListWrapper"; import ConfirmationModal from "@/components/ui/ConfirmationModal"; import ListHeader from "@/components/ui/ListHeader"; +import Modal from "@/components/ui/modal"; import { Table, TableBody, TableCell, TableHead } from "@/components/ui/table"; import TableSkeleton from "@/components/ui/TableSkeleton"; import { _TooltipDefaultParams } from "@/constants/tooltip"; import { unixToDate } from "@/utils/shared"; import { Tooltip } from "react-tooltip"; import { TableHeader, TableRow } from "../../ui/table"; -import useCmsTablePresenter from "./useCmsTablePresenter"; import CmsListFilters from "./CmsListFilters"; -import Modal from "@/components/ui/modal"; +import useCmsTablePresenter from "./useCmsTablePresenter"; const CmsTable = () => { const { @@ -95,17 +95,6 @@ const CmsTable = () => { Edit Marketing - {/* */} diff --git a/src/components/Tables/inquiries/index.tsx b/src/components/Tables/inquiries/index.tsx new file mode 100644 index 0000000..b0eff90 --- /dev/null +++ b/src/components/Tables/inquiries/index.tsx @@ -0,0 +1,116 @@ +"use client"; +import { TrashIcon } from "@/assets/icons"; +import EmptyListWrapper from "@/components/HOC/EmptyListWrapper"; +import ConfirmationModal from "@/components/ui/ConfirmationModal"; +import ListHeader from "@/components/ui/ListHeader"; +import Status from "@/components/ui/Status"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import TableSkeleton from "@/components/ui/TableSkeleton"; +import { _TooltipDefaultParams } from "@/constants/tooltip"; +import { formatPhoneNumber, unixToDate } from "@/utils/shared"; +import { Tooltip } from "react-tooltip"; +import useInquiriesListPresenter from "./useInquiriesListPresenter"; + +const InquiriesTable = () => { + const { + columns, + inquiries, + isFilterModalOpen, + setIsFilterModalOpen, + isPending, + currentInquiry, + isDeleteModalOpen, + setCurrentInquiry, + setIsDeleteModalOpen, + deleteInquiry, + } = useInquiriesListPresenter(); + return ( +
+ setIsFilterModalOpen(true)} /> + + + + {columns.map((column) => ( + + {column} + + ))} + + + {isPending && } + + + {inquiries?.map((inquiry, index) => ( + + + {index + 1} + + + {inquiry?.company_name} + + + {inquiry?.full_name} + + + {formatPhoneNumber(inquiry?.phone_number)} + + + {inquiry.status} + + + {unixToDate({ unix: inquiry.created_at, hasTime: true })} + + +
+ +
+
+
+ ))} +
+
+
+ {isDeleteModalOpen && ( + { + deleteInquiry(); + }} + onCancel={() => setIsDeleteModalOpen(false)} + /> + )} +
+ ); +}; + +export default InquiriesTable; diff --git a/src/components/Tables/inquiries/useInquiriesListPresenter.ts b/src/components/Tables/inquiries/useInquiriesListPresenter.ts new file mode 100644 index 0000000..ac9e968 --- /dev/null +++ b/src/components/Tables/inquiries/useInquiriesListPresenter.ts @@ -0,0 +1,42 @@ +"use client"; +import { + useDeleteInquiry, + useGetInquiries, +} from "@/hooks/queries/useInquiryQueries"; +import { TInquiries } from "@/lib/api"; +import { useState } from "react"; + +const useInquiriesListPresenter = () => { + const [isFilterModalOpen, setIsFilterModalOpen] = useState(false); + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + const [currentInquiry, setCurrentInquiry] = useState(null); + const columns = [ + "row", + "company name", + "full name", + "phone number", + "status", + "created at", + "actions", + ]; + const { data, isPending } = useGetInquiries(); + const { mutate } = useDeleteInquiry(() => setIsDeleteModalOpen(false)); + const deleteInquiry = () => { + if (!currentInquiry?.id) return; + mutate(currentInquiry.id); + }; + return { + columns, + inquiries: data?.data.inquiries, + isFilterModalOpen, + setIsFilterModalOpen, + isPending, + isDeleteModalOpen, + setIsDeleteModalOpen, + currentInquiry, + setCurrentInquiry, + deleteInquiry, + }; +}; + +export default useInquiriesListPresenter; diff --git a/src/hooks/queries/useInquiryQueries.ts b/src/hooks/queries/useInquiryQueries.ts new file mode 100644 index 0000000..29a72a1 --- /dev/null +++ b/src/hooks/queries/useInquiryQueries.ts @@ -0,0 +1,33 @@ +import { API_ENDPOINTS, inquiriesService } from "@/lib/api"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { useMemo } from "react"; +import { toast } from "react-toastify"; + +export const useGetInquiries = (params?: Record) => { + const queryKey = useMemo( + () => [API_ENDPOINTS.INQUIRIES.BASE(), params], + [params], + ); + return useQuery({ + queryKey, + queryFn: () => inquiriesService.getInquiries(params), + }); +}; +export const useDeleteInquiry = (successCallback?: () => void) => { + const queryClient = useQueryClient(); + const mutationKey = useMemo( + () => [API_ENDPOINTS.INQUIRIES.BASE(), "DELETE INQUIRY"], + [], + ); + return useMutation({ + mutationKey, + mutationFn: inquiriesService.deleteInquiry, + onSuccess: (response) => { + toast.success(response.message); + successCallback?.(); + queryClient.invalidateQueries({ + queryKey: [API_ENDPOINTS.INQUIRIES.BASE()], + }); + }, + }); +}; diff --git a/src/lib/api/endpoints.ts b/src/lib/api/endpoints.ts index ea16af2..b776bb2 100644 --- a/src/lib/api/endpoints.ts +++ b/src/lib/api/endpoints.ts @@ -64,4 +64,8 @@ export const API_ENDPOINTS = { CONTACT_US: { BASE: (id?: string) => (id ? `contacts/${id}` : "contacts"), }, + + INQUIRIES: { + BASE: (id?: string) => (id ? `inquiries/${id}` : "inquiries"), + } } as const; diff --git a/src/lib/api/services/index.ts b/src/lib/api/services/index.ts index 5b6896b..5149056 100644 --- a/src/lib/api/services/index.ts +++ b/src/lib/api/services/index.ts @@ -1,4 +1,5 @@ -export * from "./user-services"; export * from "./companies-service"; -export * from "./tenders-service"; +export * from "./inquiries-service"; export * from "./profile-service"; +export * from "./tenders-service"; +export * from "./user-services"; diff --git a/src/lib/api/services/inquiries-service.ts b/src/lib/api/services/inquiries-service.ts new file mode 100644 index 0000000..4b3b23c --- /dev/null +++ b/src/lib/api/services/inquiries-service.ts @@ -0,0 +1,24 @@ +import api from "../axios"; +import { API_ENDPOINTS } from "../endpoints"; +import { TInquiriesResponse } from "../types"; + +export const inquiriesService = { + getInquiries: async ( + params?: Record, + ): Promise => { + try { + return (await api.get(API_ENDPOINTS.INQUIRIES.BASE(), { params })).data; + } catch (error) { + console.error("ERROR caught in inquiries service => getInquiries", error); + throw error; + } + }, + deleteInquiry: async (id: string) => { + try { + return (await api.delete(API_ENDPOINTS.INQUIRIES.BASE(id))).data; + } catch (error) { + console.error("ERROR caught in inquiries service => deleteInquiry", error); + throw error; + } + }, +}; diff --git a/src/lib/api/types/TInquiries.ts b/src/lib/api/types/TInquiries.ts new file mode 100644 index 0000000..8cacf6d --- /dev/null +++ b/src/lib/api/types/TInquiries.ts @@ -0,0 +1,26 @@ +import { z } from "zod"; +import { createApiResponseSchema } from "./Factory"; + +const inquiriesSchema = z.object({ + company_name: z.string(), + created_at: z.number(), + full_name: z.string(), + id: z.string(), + phone_number: z.string(), + status: z.string(), + status_history: z.array( + z.object({ + changed_at: z.number(), + description: z.string(), + reason: z.string(), + status: z.string(), + }), + ), + updated_at: z.number(), + work_email: z.string(), +}); +export const inquiriesListResponse = createApiResponseSchema( + z.object({ inquiries: z.array(inquiriesSchema) }), +); +export type TInquiries = z.infer; +export type TInquiriesResponse = z.infer; diff --git a/src/lib/api/types/index.ts b/src/lib/api/types/index.ts index 1e5fa73..6a0ab72 100644 --- a/src/lib/api/types/index.ts +++ b/src/lib/api/types/index.ts @@ -6,3 +6,7 @@ export * from "./Profile"; export * from "./shared"; export * from "./TCompany"; export * from "./User"; +export * from "./TInquiries"; +export * from "./TCms"; +export * from "./Feedback"; +export * from "./Tenders"; diff --git a/src/utils/shared.ts b/src/utils/shared.ts index 61eec7a..7ff7269 100644 --- a/src/utils/shared.ts +++ b/src/utils/shared.ts @@ -8,7 +8,7 @@ export const unixToDate = ({ unix: number; hasTime?: boolean; }) => { - return moment.unix(unix).format(`YYYY/MM/DD ${hasTime ? "HH:MM" : ""}`); + return moment.unix(unix).format(`YYYY/MM/DD - ${hasTime ? "HH:MM" : ""}`); }; export const msToDate = (ms: number) => { return moment.default(ms).format("YYYY/MM/DD");