diff --git a/package-lock.json b/package-lock.json index 550951e..c260bce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "react-hook-form": "^7.62.0", "react-intersection-observer": "^9.16.0", "react-paginate": "^8.3.0", + "react-switch": "^7.1.0", "react-toastify": "^11.0.5", "tailwind-merge": "^2.6.0", "zod": "^4.1.5" @@ -5364,6 +5365,19 @@ "react": "^16 || ^17 || ^18 || ^19" } }, + "node_modules/react-switch": { + "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/react-switch/-/react-switch-7.1.0.tgz", + "integrity": "sha512-4xVeyImZE8QOTDw2FmhWz0iqo2psoRiS7XzdjaZBCIP8Dzo3rT0esHUjLee5WsAPSFXWWl1eVA5arp9n2C6yQA==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-toastify": { "version": "11.0.5", "resolved": "https://registry.npmmirror.com/react-toastify/-/react-toastify-11.0.5.tgz", diff --git a/package.json b/package.json index 091de90..6b4b741 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "react-hook-form": "^7.62.0", "react-intersection-observer": "^9.16.0", "react-paginate": "^8.3.0", + "react-switch": "^7.1.0", "react-toastify": "^11.0.5", "tailwind-merge": "^2.6.0", "zod": "^4.1.5" diff --git a/src/app/companies/categories/create/page.tsx b/src/app/companies/categories/create/page.tsx new file mode 100644 index 0000000..8f707a6 --- /dev/null +++ b/src/app/companies/categories/create/page.tsx @@ -0,0 +1,21 @@ +import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb"; +import CreateCompanyCategoryForm from "@/components/forms/companies/company-categories/CreateCompanyCategory"; +import { BreadcrumbItem } from "@/types/shared"; + +interface IProps {} + +const CreateCompanyCategoryPage = ({}: IProps) => { + const breadcrumbItems: BreadcrumbItem[] = [ + { href: "/", name: "Dashboard" }, + { href: "/companies/categories", name: "Company Categories" }, + { href: "/companies/categories/create", name: "Create Company Category" }, + ]; + return ( + <> + + + + ); +}; + +export default CreateCompanyCategoryPage; diff --git a/src/app/companies/categories/edit/[id]/page.tsx b/src/app/companies/categories/edit/[id]/page.tsx new file mode 100644 index 0000000..145abe1 --- /dev/null +++ b/src/app/companies/categories/edit/[id]/page.tsx @@ -0,0 +1,30 @@ +"use client"; +import Loading from "@/app/loading"; +import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb"; +import CreateCompanyCategoryForm from "@/components/forms/companies/company-categories/CreateCompanyCategory"; +import { useCompanyCategoriesDetails } from "@/hooks/queries"; +import { BreadcrumbItem } from "@/types/shared"; +import { use } from "react"; + +const EditCompanyCategory = ({ + params, +}: { + params: Promise<{ id: string }>; +}) => { + const { id } = use(params); + const { isLoading, data } = useCompanyCategoriesDetails(id); + const breadcrumbItems: BreadcrumbItem[] = [ + { name: "Dashboard", href: "/" }, + { name: "Company Categories", href: "/companies/categories" }, + { name: "Edit Company Category", href: `/companies/categories/edit/${id}` }, + ]; + if (isLoading) return ; + return ( + <> + + + + ); +}; + +export default EditCompanyCategory; diff --git a/src/app/companies/categories/page.tsx b/src/app/companies/categories/page.tsx new file mode 100644 index 0000000..ad64413 --- /dev/null +++ b/src/app/companies/categories/page.tsx @@ -0,0 +1,17 @@ +import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb"; +import CompanyCategoriesTable from "@/components/Tables/companies/company-categories"; + +const CompanyCategoryList = () => { + const breadcrumbItems = [ + { name: "Dashboard", href: "/" }, + { name: "Company Categories", href: "/companies/categories" }, + ]; + return ( + <> + + + + ); +}; + +export default CompanyCategoryList; diff --git a/src/components/Breadcrumbs/Breadcrumb.tsx b/src/components/Breadcrumbs/Breadcrumb.tsx index 14bb679..f49bbf3 100644 --- a/src/components/Breadcrumbs/Breadcrumb.tsx +++ b/src/components/Breadcrumbs/Breadcrumb.tsx @@ -1,10 +1,6 @@ +import { BreadcrumbItem } from "@/types/shared"; import Link from "next/link"; -interface BreadcrumbItem { - name: string; - href: string; -} - interface BreadcrumbProps { items: BreadcrumbItem[]; } diff --git a/src/components/FormElements/switch.tsx b/src/components/FormElements/switch.tsx index 1156799..4054f06 100644 --- a/src/components/FormElements/switch.tsx +++ b/src/components/FormElements/switch.tsx @@ -1,12 +1,15 @@ import { CheckIcon, XIcon } from "@/assets/icons"; import { cn } from "@/lib/utils"; -import { useId } from "react"; +import { ChangeEvent, ToggleEvent, useId } from "react"; type PropsType = { withIcon?: boolean; background?: "dark" | "light"; backgroundSize?: "sm" | "default"; name?: string; + checked?: boolean; + onToggle: (e: ToggleEvent) => void; + onChange: (e: ChangeEvent) => void; }; export function Switch({ @@ -14,6 +17,9 @@ export function Switch({ withIcon, backgroundSize, name, + checked = false, + onToggle, + onChange, }: PropsType) { const id = useId(); @@ -23,7 +29,15 @@ export function Switch({ className="flex max-w-fit cursor-pointer select-none items-center" >
- +
= z.lazy(() => z.object({ title: z.string(), @@ -36,10 +35,16 @@ export const NAV_DATA: NavData = [ items: [], }, { - title: "Companies", - url: "/companies", + title: "Manage Companies", icon: Icons.Building, - items: [], + items: [ + { Icons: Icons.Building, url: "/companies", title: "Companies" }, + { + Icons: Icons.Building, + url: "/companies/categories", + title: "Company Categories", + }, + ], }, { title: "Customers", diff --git a/src/components/Tables/companies/company-categories/index.tsx b/src/components/Tables/companies/company-categories/index.tsx new file mode 100644 index 0000000..6f2129d --- /dev/null +++ b/src/components/Tables/companies/company-categories/index.tsx @@ -0,0 +1,135 @@ +"use client"; +import { PencilSquareIcon, TrashIcon } from "@/assets/icons"; +import { Switch } from "@/components/FormElements/switch"; +import ConfirmationModal from "@/components/ui/ConfirmationModal"; +import IsVisible from "@/components/ui/IsVisible"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import TableSkeleton from "@/components/ui/TableSkeleton"; +import { unixToDate } from "@/utils/shared"; +import Link from "next/link"; +import { Skeleton } from "../../../ui/skeleton"; +import useCompanyCategoriesPresenter from "./useCompanyCategoriesPresenter"; + +const CompanyCategoriesTable = () => { + const { + categories, + isLoading, + pathName, + router, + currentCategory, + isToggling, + togglePublished, + onConfirmDelete, + setCurrentCategory, + isModalOpen, + setIsModalOpen, + } = useCompanyCategoriesPresenter(); + + return ( +
+ + + + + Row + Title + Description + Created at + Published + Actions + + + {isLoading && } + + + + +
No category found
+
+
+
+ + {categories?.map((category, index) => { + return ( + + {index + 1} + {category.name} + {category.description} + + {unixToDate(category.created_at)} + + + + + + + { + togglePublished(category.id); + }} + checked={category.published} + onToggle={(e) => { + console.log(e); + }} + /> + + + +
+ + +
+
+
+ ); + })} +
+
+
+ {isModalOpen && ( + { + onConfirmDelete(); + }} + onCancel={() => setIsModalOpen(false)} + /> + )} +
+ ); +}; + +export default CompanyCategoriesTable; diff --git a/src/components/Tables/companies/company-categories/useCompanyCategoriesPresenter.ts b/src/components/Tables/companies/company-categories/useCompanyCategoriesPresenter.ts new file mode 100644 index 0000000..472ac58 --- /dev/null +++ b/src/components/Tables/companies/company-categories/useCompanyCategoriesPresenter.ts @@ -0,0 +1,47 @@ +"use client"; +import { apiDefaultParams } from "@/constants/Api_Params"; +import { + useCompanyCategoriesQuery, + useDeleteCompanyCategoryQuery, + useToggleCompanyCategoryPublished, +} from "@/hooks/queries"; +import { TCompanyCategory } from "@/lib/api"; +import { usePathname, useRouter } from "next/navigation"; +import { useState } from "react"; + +const useCompanyCategoriesPresenter = () => { + const [isModalOpen, setIsModalOpen] = useState(false); + const [currentCategory, setCurrentCategory] = useState(); + const [params, setParams] = useState>({ + ...apiDefaultParams, + }); + const router = useRouter(); + const pathName = usePathname(); + const { data, isLoading, isError } = useCompanyCategoriesQuery(params); + const { mutate: deleteCategory } = useDeleteCompanyCategoryQuery(() => + setIsModalOpen(false), + ); + const {mutate:togglePublished,isPending:isToggling} = useToggleCompanyCategoryPublished() + const onConfirmDelete = () => { + if (!currentCategory) return; + deleteCategory(currentCategory?.id); + }; + return { + categories: data?.data.categories, + isModalOpen, + setIsModalOpen, + isLoading, + isError, + pathName, + router, + params, + setParams, + currentCategory, + togglePublished, + isToggling, + setCurrentCategory, + onConfirmDelete, + }; +}; + +export default useCompanyCategoriesPresenter; diff --git a/src/components/Tables/tenders/index.tsx b/src/components/Tables/tenders/index.tsx index bcc84da..aa43cff 100644 --- a/src/components/Tables/tenders/index.tsx +++ b/src/components/Tables/tenders/index.tsx @@ -1,6 +1,5 @@ "use client"; -import useTenderListPresenter from "./useTenderListPresenter"; import Status from "@/components/ui/Status"; import { Table, @@ -10,13 +9,12 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; +import useTenderListPresenter from "./useTenderListPresenter"; -import { TendersSkeleton } from "./Skeleton"; -import { cn } from "@/lib/utils"; -import { getStatusColor } from "@/utils/shared"; import { ExclamationIcon } from "@/assets/icons"; import Pagination from "@/components/ui/pagination"; -import Page from "../../../app/forms/form-layout/page"; +import TableSkeleton from "@/components/ui/TableSkeleton"; +import { cn } from "@/lib/utils"; import Link from "next/link"; const TendersTable = () => { const { allTenders, isPending, router, pathName, setParams } = @@ -27,9 +25,9 @@ const TendersTable = () => { - {/* + Row - */} + Title @@ -49,7 +47,7 @@ const TendersTable = () => { - {isPending && } + {isPending && } {allTenders?.data.tenders?.map((item, index) => !item ? ( @@ -59,9 +57,9 @@ const TendersTable = () => { key={item.id} className="odd:bg-gray-2 dark:odd:bg-gray-7" > - {/* + {index + 1} - */} + {item.title} diff --git a/src/components/Tables/tenders/useTenderListPresenter.ts b/src/components/Tables/tenders/useTenderListPresenter.ts index b5b071e..9c43feb 100644 --- a/src/components/Tables/tenders/useTenderListPresenter.ts +++ b/src/components/Tables/tenders/useTenderListPresenter.ts @@ -1,11 +1,11 @@ "use client"; import { ConfirmationModalProps } from "@/components/ui/ConfirmationModal"; +import { apiDefaultParams } from "@/constants/Api_Params"; import { useTendersQuery } from "@/hooks/queries"; import useDebounce from "@/hooks/useDebounce"; import { TCustomer } from "@/lib/api"; import { usePathname, useRouter } from "next/navigation"; -import { useEffect, useRef, useState } from "react"; -import { useInView } from "react-intersection-observer"; +import { useEffect, useState } from "react"; import { z } from "zod"; const useTenderListPresenter = () => { const [isModalOpen, setIsModalOpen] = useState(false); @@ -15,15 +15,13 @@ const useTenderListPresenter = () => { Partial >({}); const [params, setParams] = useState>({ - sort: "created_at", + ...apiDefaultParams, }); const router = useRouter(); const debouncedSearch = useDebounce(search, 1000); const pathName = usePathname(); const { data, error, isPending } = useTendersQuery(params); - const { ref, inView } = useInView({ threshold: 0.5 }); - useEffect(() => { const result = z.string().safeParse(debouncedSearch); if (result.success) { @@ -36,14 +34,8 @@ const useTenderListPresenter = () => { const handleFilterChange = (e: React.ChangeEvent) => { setSearch(e.target.value); }; - // const onConfirmDelete = () => { - // if (currentTender) { - // deleteTender(currentTender.id); - // } - // }; return { - ref, currentTender, setCurrentTender, search, diff --git a/src/components/forms/companies/CreateCompany.tsx b/src/components/forms/companies/CreateCompany.tsx index 834a7e5..69ac8e1 100644 --- a/src/components/forms/companies/CreateCompany.tsx +++ b/src/components/forms/companies/CreateCompany.tsx @@ -1,15 +1,14 @@ "use client"; +import { GlobeIcon, MoneyIcon } from "@/assets/icons"; +import InputGroup from "@/components/FormElements/InputGroup"; +import { TextAreaGroup } from "@/components/FormElements/InputGroup/text-area"; +import { Select } from "@/components/FormElements/select"; +import { ShowcaseSection } from "@/components/Layouts/showcase-section"; +import FormFooter from "@/components/ui/FormFooter"; +import { PHONE_REGEX } from "@/constants/regex"; +import { FormErrorMessages } from "@/constants/Texts"; import { ICreateCompanyCredentials } from "@/lib/api/types/Company"; import useCreateCompanyPresenter from "./useCreateCompanyPresenter"; -import { ShowcaseSection } from "@/components/Layouts/showcase-section"; -import { title } from "process"; -import InputGroup from "@/components/FormElements/InputGroup"; -import { FormErrorMessages } from "@/constants/Texts"; -import { Select } from "@/components/FormElements/select"; -import { CurrencyIcon, GlobeIcon, MoneyIcon } from "@/assets/icons"; -import MultiSelect from "@/components/FormElements/MultiSelect"; -import { TextAreaGroup } from "@/components/FormElements/InputGroup/text-area"; -import { PHONE_REGEX } from "@/constants/regex"; interface IProps { editMode?: boolean; @@ -18,21 +17,12 @@ interface IProps { } const CreateCompany = ({ defaultValues, editMode, id }: IProps) => { - const { - errors, - handleCancel, - handleSubmit, - isCreating, - isMutating, - isUpdating, - onSubmit, - router, - register, - } = useCreateCompanyPresenter({ - editMode, - defaultValues, - id, - }); + const { errors, handleSubmit, onSubmit, register } = + useCreateCompanyPresenter({ + editMode, + defaultValues, + id, + }); return (
{ -
+ + {/*
-
+
*/} ); }; diff --git a/src/components/forms/companies/company-categories/CreateCompanyCategory.tsx b/src/components/forms/companies/company-categories/CreateCompanyCategory.tsx new file mode 100644 index 0000000..6cd7c5d --- /dev/null +++ b/src/components/forms/companies/company-categories/CreateCompanyCategory.tsx @@ -0,0 +1,78 @@ +"use client"; +import InputGroup from "@/components/FormElements/InputGroup"; +import { TextAreaGroup } from "@/components/FormElements/InputGroup/text-area"; +import { ShowcaseSection } from "@/components/Layouts/showcase-section"; +import FormFooter from "@/components/ui/FormFooter"; +import { FormErrorMessages } from "@/constants/Texts"; +import { TCreateCompanyCategoryCredentials } from "@/lib/api"; +import useCreateCompanyCategoryPresenter from "./useCreateCompanyCategoryPresenter"; + +interface IProps { + editMode?: boolean; + defaultValues?: TCreateCompanyCategoryCredentials; + id?: string; +} + +const CreateCompanyCategoryForm = ({ defaultValues, editMode, id }: IProps) => { + const { errors, handleSubmit, onSubmit, register } = + useCreateCompanyCategoryPresenter({ defaultValues, editMode, id }); + return ( +
+ +
+ + {errors.name && ( +

{errors.name.message}

+ )} +
+
+ + {errors.description && ( +

+ {errors.description.message} +

+ )} +
+ +
+ + ); +}; + +export default CreateCompanyCategoryForm; diff --git a/src/components/forms/companies/company-categories/useCreateCompanyCategoryPresenter.ts b/src/components/forms/companies/company-categories/useCreateCompanyCategoryPresenter.ts new file mode 100644 index 0000000..9da5c7a --- /dev/null +++ b/src/components/forms/companies/company-categories/useCreateCompanyCategoryPresenter.ts @@ -0,0 +1,48 @@ +"use client"; +import { + useCreateCompanyCategory, + useUpdateCompanyCategories, +} from "@/hooks/queries"; +import { TCreateCompanyCategoryCredentials } from "@/lib/api"; +import { useRouter } from "next/navigation"; +import { SubmitHandler, useForm } from "react-hook-form"; + +interface IProps { + editMode?: boolean; + defaultValues?: TCreateCompanyCategoryCredentials; + id?: string; +} + +const useCreateCompanyCategoryPresenter = ({ + defaultValues, + editMode, + id, +}: IProps) => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + mode: "onChange", + defaultValues, + }); + const router = useRouter(); + const { mutate } = useCreateCompanyCategory(); + const { mutate: editCategory } = useUpdateCompanyCategories(id as string); + const onSubmit: SubmitHandler = (data) => { + if (editMode) { + editCategory({ id: id as string, credentials: data }); + } else { + mutate(data); + } + }; + return { + register, + onSubmit, + handleSubmit, + errors, + router, + }; +}; + +export default useCreateCompanyCategoryPresenter; diff --git a/src/components/ui/ConfirmationModal.tsx b/src/components/ui/ConfirmationModal.tsx index 6617134..38a0f15 100644 --- a/src/components/ui/ConfirmationModal.tsx +++ b/src/components/ui/ConfirmationModal.tsx @@ -1,4 +1,5 @@ "use client"; +import { useIsMutating } from "@tanstack/react-query"; import React, { useEffect, useState } from "react"; import { createPortal } from "react-dom"; import { z } from "zod"; @@ -23,6 +24,7 @@ export type ConfirmationModalProps = z.infer< >; const ConfirmationModal: React.FC = (props) => { + const isMutating = useIsMutating(); const validatedProps = ConfirmationModalPropsSchema.parse(props); const { isOpen, @@ -107,7 +109,7 @@ const ConfirmationModal: React.FC = (props) => {
-
+
= (props) => {
-
+
+ +
+ ); +}; + +export default FormFooter; diff --git a/src/components/ui/Status.tsx b/src/components/ui/Status.tsx index 273a4f7..aa311b4 100644 --- a/src/components/ui/Status.tsx +++ b/src/components/ui/Status.tsx @@ -30,18 +30,21 @@ const Status = ({ status, children }: IProps) => { return ( {children} diff --git a/src/components/ui/TableSkeleton.tsx b/src/components/ui/TableSkeleton.tsx new file mode 100644 index 0000000..9cff694 --- /dev/null +++ b/src/components/ui/TableSkeleton.tsx @@ -0,0 +1,25 @@ +import { Skeleton } from "./skeleton"; +import { TableBody, TableCell, TableRow } from "./table"; + +interface IProps { + length?: number; + column: number; +} + +const TableSkeleton = ({ column, length = 20 }: IProps) => { + return ( + + {Array.from({ length }).map((_, i) => ( + + {Array.from({ length: column }).map((_, j) => ( + + + + ))} + + ))} + + ); +}; + +export default TableSkeleton; diff --git a/src/constants/Api_Params.ts b/src/constants/Api_Params.ts new file mode 100644 index 0000000..c4af913 --- /dev/null +++ b/src/constants/Api_Params.ts @@ -0,0 +1,5 @@ +export const apiDefaultParams = { + sort: "created_at", + offset: 0, + limit: 20, +}; diff --git a/src/constants/Texts.ts b/src/constants/Texts.ts index cda0d9c..56ab881 100644 --- a/src/constants/Texts.ts +++ b/src/constants/Texts.ts @@ -9,4 +9,4 @@ export const FormErrorMessages = { invalidEmail: "Email address is invalid", alphaNumeric: "This field can only contain letters and numbers", invalidPattern: "This field does not match the required pattern", -}; +}; \ No newline at end of file diff --git a/src/hooks/queries/useCompaniesQueries.ts b/src/hooks/queries/useCompaniesQueries.ts index 839da87..4fa96dd 100644 --- a/src/hooks/queries/useCompaniesQueries.ts +++ b/src/hooks/queries/useCompaniesQueries.ts @@ -1,4 +1,8 @@ -import { API_ENDPOINTS, companiesService } from "@/lib/api"; +import { + API_ENDPOINTS, + companiesService, + companyCategoriesService, +} from "@/lib/api"; import { useInfiniteQuery, useMutation, @@ -105,3 +109,107 @@ export const useCompaniesInfiniteQuery = (params?: Record) => { }, }); }; +export const useCompanyCategoriesQuery = (params?: Record) => { + const queryKey = useMemo( + () => [API_ENDPOINTS.COMPANIES.CATEGORIES.READ_ALL, params], + [params], + ); + return useQuery({ + queryKey, + queryFn: () => companyCategoriesService.getCategories({ ...params }), + }); +}; +export const useCreateCompanyCategory = (successCallback?: () => void) => { + const queryClient = useQueryClient(); + const router = useRouter(); + const mutationKey = useMemo( + () => [API_ENDPOINTS.COMPANIES.CATEGORIES, "create"], + [], + ); + return useMutation({ + mutationKey, + mutationFn: companyCategoriesService.createCategory, + onSuccess: (response: { data: { message: string } }) => { + toast.success(response.data.message); + if (successCallback) { + successCallback(); + } + queryClient.invalidateQueries({ + queryKey: [API_ENDPOINTS.COMPANIES.CATEGORIES.READ_ALL], + }); + router.push("/companies/categories"); + }, + }); +}; +export const useDeleteCompanyCategoryQuery = (successCallback?: () => void) => { + const queryClient = useQueryClient(); + const mutationKey = useMemo( + () => [ + API_ENDPOINTS.COMPANIES.CATEGORIES.DELETE, + "delete-company-category", + ], + [], + ); + return useMutation({ + mutationKey, + mutationFn: companyCategoriesService.deleteCategory, + onSuccess: (response: { data: { message: string } }) => { + toast.success(response.data.message); + if (successCallback) { + successCallback(); + } + queryClient.invalidateQueries({ + queryKey: [API_ENDPOINTS.COMPANIES.CATEGORIES.READ_ALL], + }); + }, + }); +}; +export const useToggleCompanyCategoryPublished = () => { + const queryClient = useQueryClient(); + const mutationKey = useMemo( + () => [ + API_ENDPOINTS.COMPANIES.CATEGORIES.TOGGLE_PUBLISHED("published"), + "published", + ], + [], + ); + return useMutation({ + mutationKey, + mutationFn: companyCategoriesService.togglePublished, + onSuccess: ({ data }: { data: { message: string } }) => { + toast.success(data.message); + queryClient.invalidateQueries({ + queryKey: [API_ENDPOINTS.COMPANIES.CATEGORIES.READ_ALL], + }); + }, + }); +}; +export const useCompanyCategoriesDetails = (id: string) => { + const queryKey = useMemo( + () => [API_ENDPOINTS.COMPANIES.CATEGORIES.DETAILS(id), "details"], + [id], + ); + return useQuery({ + queryKey, + queryFn: () => companyCategoriesService.getSingleCategory(id), + }); +}; +export const useUpdateCompanyCategories = (id: string) => { + const queryClient = useQueryClient(); + const router = useRouter(); + const mutationKey = useMemo( + () => [API_ENDPOINTS.COMPANIES.CATEGORIES.UPDATE(id), "update"], + [id], + ); + return useMutation({ + mutationKey, + mutationFn: companyCategoriesService.updateCategories, + onSuccess: ({ data }: { data: { message: string } }) => { + toast.success(data.message); + queryClient.invalidateQueries({ + queryKey: [API_ENDPOINTS.COMPANIES.CATEGORIES.READ_ALL], + }); + router.push("/companies/categories"); + }, + }); +}; diff --git a/src/lib/api/endpoints.ts b/src/lib/api/endpoints.ts index 93e29b1..d747059 100644 --- a/src/lib/api/endpoints.ts +++ b/src/lib/api/endpoints.ts @@ -19,6 +19,14 @@ export const API_ENDPOINTS = { UPDATE: (id: string) => `companies/${id}`, DELETE: (id: string) => `companies/${id}`, DETAILS: (id: string) => `companies/${id}`, + CATEGORIES: { + READ_ALL: "company-categories", + CREATE: "company-categories", + UPDATE: (id: string) => `company-categories/${id}`, + DELETE: (id: string) => `company-categories/${id}`, + DETAILS: (id: string) => `company-categories/${id}`, + TOGGLE_PUBLISHED:(id:string) => `company-categories/${id}/publish`, + }, }, TENDERS: { READ_ALL: "tenders", diff --git a/src/lib/api/services/companies-service.ts b/src/lib/api/services/companies-service.ts index 02c2153..c78ce6a 100644 --- a/src/lib/api/services/companies-service.ts +++ b/src/lib/api/services/companies-service.ts @@ -4,6 +4,8 @@ import { ApiResponse, CompaniesListResponseSchema, TCompaniesResponse, + TCompanyCategoryApiResponse, + TCreateCompanyCategoryCredentials, } from "../types"; import { ICreateCompanyCredentials } from "../types"; @@ -58,3 +60,85 @@ export const companiesService = { } }, }; +export const companyCategoriesService = { + getCategories: async ( + params?: Record, + ): Promise => { + try { + const response = await api.get( + API_ENDPOINTS.COMPANIES.CATEGORIES.READ_ALL, + { + params, + }, + ); + + return response.data; + } catch (error) { + console.error("ERROR Caught in company categories service: ", error); + throw error; + } + }, + + getSingleCategory: async (id: string) => { + try { + return (await api.get(API_ENDPOINTS.COMPANIES.CATEGORIES.DETAILS(id))) + .data; + } catch (error) { + console.error( + "ERROR caught in Company categories service, Get single category", + error, + ); + throw error; + } + }, + createCategory: async (credentials: TCreateCompanyCategoryCredentials) => { + try { + const response = await api.post( + API_ENDPOINTS.COMPANIES.CATEGORIES.CREATE, + credentials, + ); + return response.data; + } catch (error) { + console.error("ERROR Caught in company categories service: ", error); + throw error; + } + }, + deleteCategory: async (id: string) => { + try { + return (await api.delete(API_ENDPOINTS.COMPANIES.CATEGORIES.DELETE(id))) + .data; + } catch (error) { + console.error( + "ERROR caught in Company Categories Services Delete:", + error, + ); + throw error; + } + }, + togglePublished: async (id: string) => { + try { + return ( + await api.patch(API_ENDPOINTS.COMPANIES.CATEGORIES.TOGGLE_PUBLISHED(id)) + ).data; + } catch (error) { + console.error( + "ERROR caught in Company Categories Services Toggle Published:", + error, + ); + throw error; + } + }, + updateCategories: async ({ + id, + credentials, + }: { + id: string; + credentials: TCreateCompanyCategoryCredentials; + }) => { + const response = await api.put( + API_ENDPOINTS.COMPANIES.CATEGORIES.UPDATE(id), + credentials, + ); + return response.data; + }, +}; diff --git a/src/lib/api/types/CompanyCategories.ts b/src/lib/api/types/CompanyCategories.ts new file mode 100644 index 0000000..8ebf64c --- /dev/null +++ b/src/lib/api/types/CompanyCategories.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import { createApiResponseSchema } from "./Factory"; +export const companyCategorySchema = z.object({ + created_at: z.number(), + description: z.string(), + id: z.string(), + name: z.string(), + published: z.boolean(), + published_at: z.number(), + thumbnail: z.string(), + updated_at: z.number(), +}); +export const createCompanyCategoryCredentials = z.object({ + name: z.string(), + description: z.string(), + thumbnail: z.string().optional(), +}); +export const companyCategoriesResponseSchema = createApiResponseSchema( + z.object({ + categories: z.array(companyCategorySchema), + }), +); +export type TCompanyCategory = z.infer; +export type TCompanyCategoryApiResponse = z.infer< + typeof companyCategoriesResponseSchema +>; +export type TCreateCompanyCategoryCredentials = z.infer< + typeof createCompanyCategoryCredentials +>; \ No newline at end of file diff --git a/src/lib/api/types/index.ts b/src/lib/api/types/index.ts index e249e49..1e5fa73 100644 --- a/src/lib/api/types/index.ts +++ b/src/lib/api/types/index.ts @@ -1,7 +1,8 @@ -export * from "./shared"; -export * from "./User"; -export * from "./Posts"; -export * from "./Factory"; -export * from "./TCompany"; +export * from "./CompanyCategories"; export * from "./Customers"; +export * from "./Factory"; +export * from "./Posts"; export * from "./Profile"; +export * from "./shared"; +export * from "./TCompany"; +export * from "./User"; diff --git a/src/types/shared.ts b/src/types/shared.ts index 97d4d70..bdfb08f 100644 --- a/src/types/shared.ts +++ b/src/types/shared.ts @@ -2,3 +2,7 @@ export interface ILabelValue { label: string; value: T; } +export interface BreadcrumbItem { + name: string; + href: string; +}