From 1d944739bc67311fae6e54e4e0d694674544b8fb Mon Sep 17 00:00:00 2001 From: AmirReza Jamali Date: Tue, 23 Sep 2025 11:16:02 +0330 Subject: [PATCH] feat(feedback): add feedback button to tenders table This commit introduces the ability for users to navigate to a feedback page directly from the tenders list. A new feedback icon and a corresponding button have been added to the actions column of the tenders table. Clicking this button redirects the user to the feedback page associated with that specific tender. To support this feature, the following changes were made: - Added a new `FeedbackIcon` SVG component. - Defined API endpoints and created query hooks for the feedback module. - Fixed a query key inconsistency for company details to ensure data is correctly refetched after updates. --- dump.rdb | Bin 88 -> 0 bytes src/app/tenders/feedback/[id]/page.tsx | 28 ++++++ src/assets/icons.tsx | 15 +++- .../Tables/tenders/feedback/index.tsx | 83 ++++++++++++++++++ .../feedback/useTenderFeedbackPresenter.ts | 20 +++++ src/components/Tables/tenders/index.tsx | 14 ++- .../forms/companies/CreateCompany.tsx | 1 - src/hooks/queries/index.ts | 1 + src/hooks/queries/useCompaniesQueries.ts | 5 +- src/hooks/queries/useFeedbackQueries.ts | 22 +++++ src/lib/api/endpoints.ts | 5 ++ src/lib/api/services/feedback-service.ts | 30 +++++++ src/lib/api/types/Feedback.ts | 16 ++++ src/lib/api/types/TCompany.ts | 2 +- src/lib/api/types/Tenders.ts | 2 +- 15 files changed, 237 insertions(+), 7 deletions(-) delete mode 100644 dump.rdb create mode 100644 src/app/tenders/feedback/[id]/page.tsx create mode 100644 src/components/Tables/tenders/feedback/index.tsx create mode 100644 src/components/Tables/tenders/feedback/useTenderFeedbackPresenter.ts create mode 100644 src/hooks/queries/useFeedbackQueries.ts create mode 100644 src/lib/api/services/feedback-service.ts create mode 100644 src/lib/api/types/Feedback.ts diff --git a/dump.rdb b/dump.rdb deleted file mode 100644 index 7c4e74d2c5092ed155e6c72d1ae1aa8678d45bdc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 88 zcmWG?b@2=~FfcUy#aWb^l3A=; +} + +const TenderFeedbackPage = ({ params }: IProps) => { + const { id } = use(params); + + const breadcrumbItems: BreadcrumbItem[] = [ + { href: "/", name: "Dashboard" }, + { href: "/tenders", name: "Tenders" }, + { href: `/tenders/feedback/${id}`, name: "Tender feedback" }, + ]; + return ( + <> + + + + ); +}; + +export default TenderFeedbackPage; diff --git a/src/assets/icons.tsx b/src/assets/icons.tsx index 7c3406b..4b3433d 100644 --- a/src/assets/icons.tsx +++ b/src/assets/icons.tsx @@ -297,7 +297,20 @@ export function EmailIcon(props: IconProps) { ); } - +export function FeedbackIcon(props: IconProps) { + return ( + + + + ); +} export function PasswordIcon(props: IconProps) { return ( { + const { feedback, isLoading, columns, pathName, router } = + useTenderFeedbackPresenter({ id }); + return ( +
+ + + + {columns.map((column) => ( + + {column} + + ))} + + + {isLoading && } + + {feedback?.map((item, index) => + !item ? ( +
No feedback is submitted for this tender
+ ) : ( + + + {index + 1} + + + {item.feedback_type} + + + {unixToDate(item.updated_at)} + + + {item?.tender?.title ?? "-"} + + + {unixToDate(item.created_at)} + + + + + + ), + )} +
+
+
+ ); +}; + +export default TenderFeedbackTable; diff --git a/src/components/Tables/tenders/feedback/useTenderFeedbackPresenter.ts b/src/components/Tables/tenders/feedback/useTenderFeedbackPresenter.ts new file mode 100644 index 0000000..07fbec3 --- /dev/null +++ b/src/components/Tables/tenders/feedback/useTenderFeedbackPresenter.ts @@ -0,0 +1,20 @@ +"use client"; +import { useGetFeedback } from "@/hooks/queries"; +import { usePathname, useRouter } from "next/navigation"; + +const useTenderFeedbackPresenter = ({ id }: { id: string }) => { + const { data, isLoading } = useGetFeedback({ tender_id: id }); + const router = useRouter(); + const pathName = usePathname(); + const columns = [ + "row", + "feedback type", + "updated at", + "tender name", + "created at", + "actions", + ]; + return { feedback: data?.data, isLoading, columns, pathName, router }; +}; + +export default useTenderFeedbackPresenter; diff --git a/src/components/Tables/tenders/index.tsx b/src/components/Tables/tenders/index.tsx index ebd76b4..3518cba 100644 --- a/src/components/Tables/tenders/index.tsx +++ b/src/components/Tables/tenders/index.tsx @@ -11,7 +11,7 @@ import { } from "@/components/ui/table"; import useTenderListPresenter from "./useTenderListPresenter"; -import { ExclamationIcon } from "@/assets/icons"; +import { ExclamationIcon, FeedbackIcon } from "@/assets/icons"; import Pagination from "@/components/ui/pagination"; import TableSkeleton from "@/components/ui/TableSkeleton"; import { cn } from "@/lib/utils"; @@ -65,7 +65,10 @@ const TendersTable = () => { {item.status} - + + ), diff --git a/src/components/forms/companies/CreateCompany.tsx b/src/components/forms/companies/CreateCompany.tsx index d4d9793..51c8992 100644 --- a/src/components/forms/companies/CreateCompany.tsx +++ b/src/components/forms/companies/CreateCompany.tsx @@ -528,7 +528,6 @@ const CreateCompany = ({ defaultValues, editMode, id }: IProps) => { - ); }; diff --git a/src/hooks/queries/index.ts b/src/hooks/queries/index.ts index 5443ab8..08dff37 100644 --- a/src/hooks/queries/index.ts +++ b/src/hooks/queries/index.ts @@ -3,3 +3,4 @@ export * from "./useCompaniesQueries"; export * from "./useTenders"; export * from "./useCustomerQueries"; export * from "./useProfileQueries"; +export * from "./useFeedbackQueries" \ No newline at end of file diff --git a/src/hooks/queries/useCompaniesQueries.ts b/src/hooks/queries/useCompaniesQueries.ts index 4fa96dd..e8ab92a 100644 --- a/src/hooks/queries/useCompaniesQueries.ts +++ b/src/hooks/queries/useCompaniesQueries.ts @@ -45,6 +45,9 @@ export const useUpdateCompany = (id: string) => { queryClient.invalidateQueries({ queryKey: [API_ENDPOINTS.COMPANIES.READ_ALL], }); + queryClient.invalidateQueries({ + queryKey: [API_ENDPOINTS.COMPANIES.READ_ALL, "details"], + }); router.push("/companies"); }, }); @@ -52,7 +55,7 @@ export const useUpdateCompany = (id: string) => { export const useCompanyDetails = (id: string) => { const queryKey = useMemo( - () => [API_ENDPOINTS.COMPANIES.DETAILS(id), "details"], + () => [API_ENDPOINTS.COMPANIES.READ_ALL, "details"], [id], ); return useQuery({ diff --git a/src/hooks/queries/useFeedbackQueries.ts b/src/hooks/queries/useFeedbackQueries.ts new file mode 100644 index 0000000..31dcec0 --- /dev/null +++ b/src/hooks/queries/useFeedbackQueries.ts @@ -0,0 +1,22 @@ +import { API_ENDPOINTS } from "@/lib/api"; +import { feedbackService } from "@/lib/api/services/feedback-service"; +import { useQuery } from "@tanstack/react-query"; +import { useMemo } from "react"; + +export const useGetFeedSingleFeedback = (id: string) => { + const queryKey = useMemo(() => [API_ENDPOINTS.FEEDBACK.DETAILS(id)], [id]); + return useQuery({ + queryKey, + queryFn: () => feedbackService.getSingleFeedback(id), + }); +}; +export const useGetFeedback = (params: Record) => { + const queryKey = useMemo( + () => [API_ENDPOINTS.FEEDBACK.READ_ALL, params], + [params], + ); + return useQuery({ + queryKey, + queryFn: () => feedbackService.getFeedbacks(params), + }); +}; diff --git a/src/lib/api/endpoints.ts b/src/lib/api/endpoints.ts index d168ab6..587cb86 100644 --- a/src/lib/api/endpoints.ts +++ b/src/lib/api/endpoints.ts @@ -45,4 +45,9 @@ export const API_ENDPOINTS = { ASSIGN_COMPANY_TO_CUSTOMER: (id: string) => `customers/${id}/companies/assign`, }, + FEEDBACK: { + READ_ALL: "feedback", + DETAILS: (id: string) => `feedback/${id}`, + DELETE: (id: string) => `feedback/${id}`, + }, } as const; diff --git a/src/lib/api/services/feedback-service.ts b/src/lib/api/services/feedback-service.ts new file mode 100644 index 0000000..8319194 --- /dev/null +++ b/src/lib/api/services/feedback-service.ts @@ -0,0 +1,30 @@ +import api from "../axios"; +import { API_ENDPOINTS } from "../endpoints"; +import { ApiResponse } from "../types"; +import { TFeedbackResponse } from "../types/Feedback"; + +export const feedbackService = { + getFeedbacks: async ( + params: Record, + ): Promise => { + try { + return (await api.get(API_ENDPOINTS.FEEDBACK.READ_ALL, { params })).data; + } catch (error) { + console.error("ERROR Caught in feedback service => getFeedbacks", error); + throw error; + } + }, + getSingleFeedback: async ( + id: string, + ): Promise> => { + try { + return (await api.get(API_ENDPOINTS.FEEDBACK.DETAILS(id))).data; + } catch (error) { + console.error( + "ERROR Caught in feedback service => getSingleFeedback", + error, + ); + throw error; + } + }, +}; diff --git a/src/lib/api/types/Feedback.ts b/src/lib/api/types/Feedback.ts new file mode 100644 index 0000000..3d0ef34 --- /dev/null +++ b/src/lib/api/types/Feedback.ts @@ -0,0 +1,16 @@ +import { z } from "zod"; +import { TenderSchema } from "./Tenders"; +export const feedbackSchema = z.object({ + company_id: z.string(), + created_at: z.number(), + customer_id: z.string(), + feedback_type: z.string(), + id: z.string(), + tender: TenderSchema.or(z.null()), + updated_at: z.number(), +}); +export const feedbackResponseSchema = z.object({ + data: z.array(feedbackSchema), +}); +export type TFeedback = z.infer; +export type TFeedbackResponse = z.infer; diff --git a/src/lib/api/types/TCompany.ts b/src/lib/api/types/TCompany.ts index b171b3d..8e5a9e0 100644 --- a/src/lib/api/types/TCompany.ts +++ b/src/lib/api/types/TCompany.ts @@ -52,7 +52,7 @@ export interface ICreateCompanyCredentials { export const companySchema = z.object({ id: z.string(), name: z.string(), - email: z.string().email().optional(), + email: z.string().optional(), phone: z.string().optional(), website: z.string(), type: z.string(), diff --git a/src/lib/api/types/Tenders.ts b/src/lib/api/types/Tenders.ts index 3ffb21c..4d38f4d 100644 --- a/src/lib/api/types/Tenders.ts +++ b/src/lib/api/types/Tenders.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -const TenderSchema = z.object({ +export const TenderSchema = z.object({ id: z.string(), buyer_organization: z.object({ name: z.string(),