Merge pull request 'feat(feedback): add feedback button to tenders table' (#34) from tender-feedbacks into develop
Reviewed-on: https://repo.ravanertebat.com/TM/tm_panel/pulls/34 Reviewed-by: Sina Nakhostin <s.nakhostin@ravanertebat.com>
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
"use client";
|
||||
import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb";
|
||||
import TenderFeedbackTable from "@/components/Tables/tenders/feedback";
|
||||
import { BreadcrumbItem } from "@/types/shared";
|
||||
import { use } from "react";
|
||||
interface IProps {
|
||||
params: Promise<{
|
||||
id: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
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 (
|
||||
<>
|
||||
<Breadcrumb items={breadcrumbItems} />
|
||||
<TenderFeedbackTable id={id} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TenderFeedbackPage;
|
||||
+14
-1
@@ -297,7 +297,20 @@ export function EmailIcon(props: IconProps) {
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function FeedbackIcon(props: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="24px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="24px"
|
||||
fill="currentColor"
|
||||
{...props}
|
||||
>
|
||||
<path d="M480-360q17 0 28.5-11.5T520-400q0-17-11.5-28.5T480-440q-17 0-28.5 11.5T440-400q0 17 11.5 28.5T480-360Zm-40-160h80v-240h-80v240ZM80-80v-720q0-33 23.5-56.5T160-880h640q33 0 56.5 23.5T880-800v480q0 33-23.5 56.5T800-240H240L80-80Zm126-240h594v-480H160v525l46-45Zm-46 0v-480 480Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
export function PasswordIcon(props: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
"use client";
|
||||
|
||||
import { ExclamationIcon } from "@/assets/icons";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
} from "@/components/ui/table";
|
||||
import TableSkeleton from "@/components/ui/TableSkeleton";
|
||||
import { unixToDate } from "@/utils/shared";
|
||||
import { TableRow } from "../../../ui/table";
|
||||
import useTenderFeedbackPresenter from "./useTenderFeedbackPresenter";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface IProps {
|
||||
id: string;
|
||||
}
|
||||
|
||||
const TenderFeedbackTable = ({ id }: IProps) => {
|
||||
const { feedback, isLoading, columns, pathName, router } =
|
||||
useTenderFeedbackPresenter({ id });
|
||||
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">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
{columns.map((column) => (
|
||||
<TableHead className="capitalize" key={column} colSpan={100}>
|
||||
{column}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
{isLoading && <TableSkeleton column={columns.length} />}
|
||||
<TableBody>
|
||||
{feedback?.map((item, index) =>
|
||||
!item ? (
|
||||
<h6 key={index}>No feedback is submitted for this tender</h6>
|
||||
) : (
|
||||
<TableRow
|
||||
key={item.id}
|
||||
className="odd:bg-gray-2 dark:odd:bg-gray-7"
|
||||
>
|
||||
<TableCell className="text-start" colSpan={100}>
|
||||
{index + 1}
|
||||
</TableCell>
|
||||
<TableCell className="uppercase" colSpan={100}>
|
||||
{item.feedback_type}
|
||||
</TableCell>
|
||||
<TableCell className="uppercase" colSpan={100}>
|
||||
{unixToDate(item.updated_at)}
|
||||
</TableCell>
|
||||
<TableCell className="uppercase" colSpan={100}>
|
||||
{item?.tender?.title ?? "-"}
|
||||
</TableCell>
|
||||
<TableCell className="uppercase" colSpan={100}>
|
||||
{unixToDate(item.created_at)}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
className={cn(`text flex gap-2 capitalize`)}
|
||||
colSpan={100}
|
||||
>
|
||||
<button
|
||||
className="text-gray-40 rounded-full bg-gray-dark bg-opacity-5 hover:text-green-dark dark:bg-gray dark:bg-opacity-5"
|
||||
onClick={() => {
|
||||
router.push(`${pathName}/${item.id}`);
|
||||
}}
|
||||
>
|
||||
<ExclamationIcon />
|
||||
</button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
),
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TenderFeedbackTable;
|
||||
@@ -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;
|
||||
@@ -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 = () => {
|
||||
<TableCell className={cn(`text capitalize`)} colSpan={100}>
|
||||
<Status status={item.status}>{item.status}</Status>
|
||||
</TableCell>
|
||||
<TableCell className={cn(`text capitalize`)} colSpan={100}>
|
||||
<TableCell
|
||||
className={cn(`text flex gap-2 capitalize`)}
|
||||
colSpan={100}
|
||||
>
|
||||
<button
|
||||
className="text-gray-40 rounded-full bg-gray-dark bg-opacity-5 hover:text-green-dark dark:bg-gray dark:bg-opacity-5"
|
||||
onClick={() => {
|
||||
@@ -74,6 +77,13 @@ const TendersTable = () => {
|
||||
>
|
||||
<ExclamationIcon />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
router.push(`${pathName}/feedback/${item.id}`);
|
||||
}}
|
||||
>
|
||||
<FeedbackIcon />
|
||||
</button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
),
|
||||
|
||||
@@ -528,7 +528,6 @@ const CreateCompany = ({ defaultValues, editMode, id }: IProps) => {
|
||||
</div>
|
||||
|
||||
<FormFooter />
|
||||
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,3 +3,4 @@ export * from "./useCompaniesQueries";
|
||||
export * from "./useTenders";
|
||||
export * from "./useCustomerQueries";
|
||||
export * from "./useProfileQueries";
|
||||
export * from "./useFeedbackQueries"
|
||||
@@ -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({
|
||||
|
||||
@@ -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<string, any>) => {
|
||||
const queryKey = useMemo(
|
||||
() => [API_ENDPOINTS.FEEDBACK.READ_ALL, params],
|
||||
[params],
|
||||
);
|
||||
return useQuery({
|
||||
queryKey,
|
||||
queryFn: () => feedbackService.getFeedbacks(params),
|
||||
});
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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<string, any>,
|
||||
): Promise<TFeedbackResponse> => {
|
||||
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<ApiResponse<TFeedbackResponse>> => {
|
||||
try {
|
||||
return (await api.get(API_ENDPOINTS.FEEDBACK.DETAILS(id))).data;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"ERROR Caught in feedback service => getSingleFeedback",
|
||||
error,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -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<typeof feedbackSchema>;
|
||||
export type TFeedbackResponse = z.infer<typeof feedbackResponseSchema>;
|
||||
@@ -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(),
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user