Merge pull request 'feat(feedback): Add feedback view for companies and customers' (#36) from feedbacks into develop
Reviewed-on: https://repo.ravanertebat.com/TM/tm_panel/pulls/36
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
"use client";
|
||||
import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb";
|
||||
import FeedbackTable from "@/components/Tables/feedback-table";
|
||||
|
||||
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: "/companies", name: "Companies" },
|
||||
{ href: `/companies/feedback/${id}`, name: "Companies feedback" },
|
||||
];
|
||||
return (
|
||||
<>
|
||||
<Breadcrumb items={breadcrumbItems} />
|
||||
<FeedbackTable paramKey="company_id" id={id} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TenderFeedbackPage;
|
||||
@@ -0,0 +1,29 @@
|
||||
"use client";
|
||||
import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb";
|
||||
import FeedbackTable from "@/components/Tables/feedback-table";
|
||||
|
||||
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: "/customers", name: "Customers" },
|
||||
{ href: `/customers/feedback/${id}`, name: "Customers feedback" },
|
||||
];
|
||||
return (
|
||||
<>
|
||||
<Breadcrumb items={breadcrumbItems} />
|
||||
<FeedbackTable paramKey="customer_id" id={id} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TenderFeedbackPage;
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb";
|
||||
import TenderFeedbackTable from "@/components/Tables/tenders/feedback";
|
||||
import FeedbackTable from "@/components/Tables/feedback-table";
|
||||
|
||||
import { BreadcrumbItem } from "@/types/shared";
|
||||
import { use } from "react";
|
||||
interface IProps {
|
||||
@@ -20,7 +21,7 @@ const TenderFeedbackPage = ({ params }: IProps) => {
|
||||
return (
|
||||
<>
|
||||
<Breadcrumb items={breadcrumbItems} />
|
||||
<TenderFeedbackTable id={id} />
|
||||
<FeedbackTable id={id} paramKey="tender_id" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+15
-15
@@ -214,7 +214,20 @@ export function GlobeIcon(props: IconProps) {
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function EyeIcon(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-320q75 0 127.5-52.5T660-500q0-75-52.5-127.5T480-680q-75 0-127.5 52.5T300-500q0 75 52.5 127.5T480-320Zm0-72q-45 0-76.5-31.5T372-500q0-45 31.5-76.5T480-608q45 0 76.5 31.5T588-500q0 45-31.5 76.5T480-392Zm0 192q-146 0-266-81.5T40-500q54-137 174-218.5T480-800q146 0 266 81.5T920-500q-54 137-174 218.5T480-200Zm0-300Zm0 220q113 0 207.5-59.5T832-500q-50-101-144.5-160.5T480-720q-113 0-207.5 59.5T128-500q50 101 144.5 160.5T480-280Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
export function UserSettingIcon(props: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
@@ -297,20 +310,7 @@ 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
|
||||
|
||||
@@ -1,30 +1,32 @@
|
||||
"use client";
|
||||
import { PasswordIcon, UserIcon } from "@/assets/icons";
|
||||
|
||||
import InputGroup from "../FormElements/InputGroup";
|
||||
import { SubmitHandler, useForm } from "react-hook-form";
|
||||
import { ILoginCredentials } from "@/lib/api";
|
||||
import { useLoginQuery } from "@/hooks/queries";
|
||||
import { useRef, useState } from "react";
|
||||
import { ILoginCredentials } from "@/lib/api";
|
||||
import { useState } from "react";
|
||||
import { SubmitHandler, useForm } from "react-hook-form";
|
||||
import InputGroup from "../FormElements/InputGroup";
|
||||
|
||||
import { FormErrorMessages } from "@/constants/Texts";
|
||||
import { useIsMutating } from "@tanstack/react-query";
|
||||
|
||||
export default function SigninWithPassword() {
|
||||
const isMutating = useIsMutating();
|
||||
const [passwordFieldInputType, setPasswordFieldInputType] = useState<
|
||||
"password" | "text"
|
||||
>("password");
|
||||
const { handleSubmit, control, reset, register } =
|
||||
useForm<ILoginCredentials>();
|
||||
const { mutate, isPending } = useLoginQuery();
|
||||
const { mutate } = useLoginQuery(reset);
|
||||
|
||||
const onSubmit: SubmitHandler<ILoginCredentials> = (data) => {
|
||||
mutate(data);
|
||||
reset();
|
||||
};
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<InputGroup
|
||||
type="username"
|
||||
disabled={!!isMutating}
|
||||
{...register("username", {
|
||||
required: {
|
||||
value: true,
|
||||
@@ -40,6 +42,7 @@ export default function SigninWithPassword() {
|
||||
|
||||
<InputGroup
|
||||
type={passwordFieldInputType}
|
||||
disabled={!!isMutating}
|
||||
{...register("password", {
|
||||
required: {
|
||||
value: true,
|
||||
@@ -67,7 +70,11 @@ export default function SigninWithPassword() {
|
||||
type="submit"
|
||||
className="flex w-full cursor-pointer items-center justify-center gap-2 rounded-lg bg-primary p-4 font-medium text-white transition hover:bg-opacity-90"
|
||||
>
|
||||
Sign In
|
||||
{isMutating ? (
|
||||
<div className="h-5 w-5 animate-spin rounded-full border-b-2 border-gray-1"></div>
|
||||
) : (
|
||||
"Sign in"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -28,6 +28,7 @@ type TagInputProps<T extends FieldValues> = {
|
||||
watch?: UseFormWatch<T>;
|
||||
errors?: FieldErrors<T>;
|
||||
maxTags?: number;
|
||||
shouldAddWithSpace?: boolean;
|
||||
allowDuplicates?: boolean;
|
||||
tagValidator?: (tag: string) => boolean;
|
||||
} & Partial<UseFormRegisterReturn>;
|
||||
@@ -43,6 +44,7 @@ const TagInput = <T extends FieldValues>({
|
||||
name,
|
||||
register,
|
||||
setValue,
|
||||
shouldAddWithSpace = false,
|
||||
watch,
|
||||
errors,
|
||||
maxTags,
|
||||
@@ -87,7 +89,11 @@ const TagInput = <T extends FieldValues>({
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter" || e.key === ",") {
|
||||
if (
|
||||
e.key === "Enter" ||
|
||||
e.key === "," ||
|
||||
(shouldAddWithSpace && e.key === " ")
|
||||
) {
|
||||
e.preventDefault();
|
||||
addTag(inputValue);
|
||||
} else if (e.key === "Backspace" && !inputValue && tags.length > 0) {
|
||||
@@ -167,7 +173,7 @@ const TagInput = <T extends FieldValues>({
|
||||
<input
|
||||
type="hidden"
|
||||
{...(register ? register(name) : {})}
|
||||
value={tags.length > 0 ? JSON.stringify(tags) : ''}
|
||||
value={tags.length > 0 ? JSON.stringify(tags) : ""}
|
||||
ref={ref}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -24,7 +24,7 @@ export function Header() {
|
||||
{isMobile && (
|
||||
<Link href={"/"} className="ml-2 max-[430px]:hidden min-[375px]:ml-4">
|
||||
<Image
|
||||
src={"/images/logo/logo-icon.svg"}
|
||||
src={"/images/fav-icon.svg"}
|
||||
width={32}
|
||||
height={32}
|
||||
alt=""
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"use client";
|
||||
import { PencilSquareIcon, TrashIcon } from "@/assets/icons";
|
||||
import { ExclamationIcon, PencilSquareIcon, TrashIcon } from "@/assets/icons";
|
||||
import ConfirmationModal from "@/components/ui/ConfirmationModal";
|
||||
import {
|
||||
Table,
|
||||
@@ -58,39 +58,33 @@ const CompaniesTable = ({}: IProps) => {
|
||||
key={company.id}
|
||||
className="odd:bg-gray-2 dark:odd:bg-gray-7"
|
||||
>
|
||||
<TableHead className="text-start" colSpan={100}>
|
||||
<TableCell className="text-start" colSpan={100}>
|
||||
{index + 1}
|
||||
</TableHead>
|
||||
<TableHead className="text-start" colSpan={100}>
|
||||
</TableCell>
|
||||
<TableCell className="text-start" colSpan={100}>
|
||||
{company.name}
|
||||
</TableHead>
|
||||
<TableHead className="text-start" colSpan={100}>
|
||||
</TableCell>
|
||||
<TableCell className="text-start" colSpan={100}>
|
||||
{company.email}
|
||||
</TableHead>
|
||||
<TableHead className="w-full text-start" colSpan={100}>
|
||||
</TableCell>
|
||||
<TableCell className="w-full text-start" colSpan={100}>
|
||||
{formatPhoneNumber(company.phone)}
|
||||
</TableHead>
|
||||
<TableHead className="text-start" colSpan={100}>
|
||||
</TableCell>
|
||||
<TableCell className="text-start" colSpan={100}>
|
||||
{company.address.country}
|
||||
</TableHead>
|
||||
<TableHead className="text-start" colSpan={100}>
|
||||
</TableCell>
|
||||
<TableCell className="text-start" colSpan={100}>
|
||||
{company.address.state}
|
||||
</TableHead>
|
||||
<TableHead className="text-start" colSpan={100}>
|
||||
{company.address.city}
|
||||
</TableHead>
|
||||
<TableHead className="text-start" colSpan={100}>
|
||||
{company.address.postal_code}
|
||||
</TableHead>
|
||||
<TableHead className="text-start" colSpan={100}>
|
||||
</TableCell>
|
||||
<TableCell className="text-start" colSpan={100}>
|
||||
{company.language}
|
||||
</TableHead>
|
||||
<TableHead className="text-start" colSpan={100}>
|
||||
</TableCell>
|
||||
<TableCell className="text-start" colSpan={100}>
|
||||
{company.currency}
|
||||
</TableHead>
|
||||
<TableHead className="text-start" colSpan={100}>
|
||||
</TableCell>
|
||||
<TableCell className="text-start" colSpan={100}>
|
||||
{company.employee_count}
|
||||
</TableHead>
|
||||
</TableCell>
|
||||
<TableCell className="text-start xl:pr-7.5">
|
||||
<div className="flex items-center justify-start gap-x-3.5">
|
||||
<button
|
||||
@@ -112,6 +106,14 @@ const CompaniesTable = ({}: IProps) => {
|
||||
<span className="sr-only">Edit Company </span>
|
||||
<PencilSquareIcon />
|
||||
</button>
|
||||
<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}/feedback/${company.id}`);
|
||||
}}
|
||||
>
|
||||
<ExclamationIcon />
|
||||
</button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -31,8 +31,7 @@ export const useCompanyListPresenter = () => {
|
||||
"phone",
|
||||
"country",
|
||||
"state",
|
||||
"city",
|
||||
"postal code",
|
||||
|
||||
"language",
|
||||
"currency",
|
||||
"employee count",
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import {
|
||||
Dispatch,
|
||||
FC,
|
||||
RefObject,
|
||||
SetStateAction,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import { Dispatch, FC, SetStateAction, useEffect, useState } from "react";
|
||||
|
||||
import { useForm } from "react-hook-form";
|
||||
import { TAssignCustomerToCompanyCredentials } from "@/lib/api";
|
||||
import { useCompanyFullList } from "@/hooks/queries";
|
||||
import { ILabelValue } from "@/types/shared";
|
||||
import { Select } from "@/components/FormElements/select";
|
||||
import { Building } from "@/components/Layouts/sidebar/icons";
|
||||
import { FormErrorMessages } from "@/constants/Texts";
|
||||
import { useCompanyFullList } from "@/hooks/queries";
|
||||
import { TAssignCustomerToCompanyCredentials } from "@/lib/api";
|
||||
import { ILabelValue } from "@/types/shared";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
interface IProps {
|
||||
setCompanies: Dispatch<
|
||||
SetStateAction<TAssignCustomerToCompanyCredentials | null>
|
||||
SetStateAction<TAssignCustomerToCompanyCredentials | undefined>
|
||||
>;
|
||||
defaultValues?: TAssignCustomerToCompanyCredentials;
|
||||
}
|
||||
const AssignToCompanyModalContent: FC<IProps> = ({ setCompanies }) => {
|
||||
const AssignToCompanyModalContent: FC<IProps> = ({
|
||||
setCompanies,
|
||||
defaultValues,
|
||||
}) => {
|
||||
const [options, setOptions] = useState<ILabelValue[]>([]);
|
||||
|
||||
const { data, status, isSuccess } = useCompanyFullList();
|
||||
const { handleSubmit, register } =
|
||||
useForm<TAssignCustomerToCompanyCredentials>();
|
||||
useForm<TAssignCustomerToCompanyCredentials>({
|
||||
mode: "onChange",
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"use client";
|
||||
import { PencilSquareIcon, TrashIcon } from "@/assets/icons";
|
||||
import { ExclamationIcon, PencilSquareIcon, TrashIcon } from "@/assets/icons";
|
||||
import ConfirmationModal from "@/components/ui/ConfirmationModal";
|
||||
import {
|
||||
Table,
|
||||
@@ -112,11 +112,22 @@ const CustomersTable = () => {
|
||||
onClick={() => {
|
||||
setCurrentCustomer(customer);
|
||||
setIsAssignCompanyModalOpen(true);
|
||||
setSelectedCompanies({
|
||||
company_ids: [customer?.companies?.[0]?.id ?? ""],
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Assign To Company </span>
|
||||
<Building />
|
||||
</button>
|
||||
<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}/feedback/${customer.id}`);
|
||||
}}
|
||||
>
|
||||
<ExclamationIcon />
|
||||
</button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -137,7 +148,10 @@ const CustomersTable = () => {
|
||||
isOpen={isAssignCompanyModalOpen}
|
||||
onClose={() => setIsAssignCompanyModalOpen(false)}
|
||||
>
|
||||
<AssignToCompanyModalContent setCompanies={setSelectedCompanies} />
|
||||
<AssignToCompanyModalContent
|
||||
setCompanies={setSelectedCompanies}
|
||||
defaultValues={selectedCompanies}
|
||||
/>
|
||||
</Modal>
|
||||
{isModalOpen && (
|
||||
<ConfirmationModal
|
||||
|
||||
@@ -12,8 +12,9 @@ import { useState } from "react";
|
||||
const useCustomerListPresenter = () => {
|
||||
const [isAssignCompanyModalOpen, setIsAssignCompanyModalOpen] =
|
||||
useState(false);
|
||||
const [selectedCompanies, setSelectedCompanies] =
|
||||
useState<TAssignCustomerToCompanyCredentials | null>(null);
|
||||
const [selectedCompanies, setSelectedCompanies] = useState<
|
||||
TAssignCustomerToCompanyCredentials | undefined
|
||||
>();
|
||||
const [currentCustomer, setCurrentCustomer] = useState<TCustomer | null>(
|
||||
null,
|
||||
);
|
||||
@@ -50,6 +51,7 @@ const useCustomerListPresenter = () => {
|
||||
deleteCustomer(currentCustomer.id);
|
||||
}
|
||||
};
|
||||
|
||||
const customers = data?.pages.flatMap((page) => page.data.customers) ?? [];
|
||||
return {
|
||||
allCustomers: customers,
|
||||
|
||||
+30
-17
@@ -1,31 +1,39 @@
|
||||
"use client";
|
||||
|
||||
import { ExclamationIcon } from "@/assets/icons";
|
||||
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import TableSkeleton from "@/components/ui/TableSkeleton";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { unixToDate } from "@/utils/shared";
|
||||
import { TableRow } from "../../../ui/table";
|
||||
import useTenderFeedbackPresenter from "./useTenderFeedbackPresenter";
|
||||
import { capitalize, unixToDate } from "@/utils/shared";
|
||||
|
||||
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
|
||||
import useFeedbackTablePresenter from "./useFeedbackTablePresenter";
|
||||
interface IProps {
|
||||
id: string;
|
||||
paramKey: "tender_id" | "company_id" | "customer_id";
|
||||
}
|
||||
const FeedbackTable = ({ id, paramKey }: IProps) => {
|
||||
const {
|
||||
columns,
|
||||
feedback,
|
||||
isLoading,
|
||||
pathName,
|
||||
router,
|
||||
getShowcaseSectionTitle,
|
||||
} = useFeedbackTablePresenter({ id, paramKey });
|
||||
|
||||
const TenderFeedbackTable = ({ id }: IProps) => {
|
||||
const { feedback, isLoading, columns, pathName, router } =
|
||||
useTenderFeedbackPresenter({ id });
|
||||
return (
|
||||
<ShowcaseSection
|
||||
title={!isLoading && `Tender: ${feedback?.[0].tender?.title}`}
|
||||
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"
|
||||
title={
|
||||
!isLoading &&
|
||||
`${capitalize(paramKey.slice(0, -3))}${getShowcaseSectionTitle()}`
|
||||
}
|
||||
className="flex flex-col rounded-[10px] bg-white px-7.5 pb-4 pt-7.5 capitalize shadow-1 dark:bg-gray-dark dark:shadow-card"
|
||||
>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
@@ -38,11 +46,16 @@ const TenderFeedbackTable = ({ id }: IProps) => {
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
{isLoading && <TableSkeleton column={columns.length} />}
|
||||
|
||||
<TableBody>
|
||||
{feedback?.map((item, index) =>
|
||||
!item ? (
|
||||
<h6 key={index}>No feedback is submitted for this tender</h6>
|
||||
) : (
|
||||
{!feedback?.length ? (
|
||||
<TableRow className="w-full">
|
||||
<TableCell colSpan={500} className="text-center">
|
||||
<h2>No feedback</h2>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
feedback?.map((item, index) => (
|
||||
<TableRow
|
||||
key={item.id}
|
||||
className="odd:bg-gray-2 dark:odd:bg-gray-7"
|
||||
@@ -74,7 +87,7 @@ const TenderFeedbackTable = ({ id }: IProps) => {
|
||||
</button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
),
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
@@ -82,4 +95,4 @@ const TenderFeedbackTable = ({ id }: IProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default TenderFeedbackTable;
|
||||
export default FeedbackTable;
|
||||
@@ -0,0 +1,36 @@
|
||||
import { useGetFeedback } from "@/hooks/queries";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
|
||||
interface IProps {
|
||||
id: string;
|
||||
paramKey: "tender_id" | "company_id" | "customer_id";
|
||||
}
|
||||
const useFeedbackTablePresenter = ({ id, paramKey }: IProps) => {
|
||||
const { data, isLoading } = useGetFeedback({ [paramKey]: id });
|
||||
const router = useRouter();
|
||||
const pathName = usePathname();
|
||||
const columns = [
|
||||
"row",
|
||||
"feedback type",
|
||||
"updated at",
|
||||
"created at",
|
||||
"actions",
|
||||
];
|
||||
const getShowcaseSectionTitle = () => {
|
||||
if (paramKey === "tender_id") {
|
||||
return `: ${data?.data?.[0]?.tender?.title}`;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
return {
|
||||
isLoading,
|
||||
feedback: data?.data,
|
||||
columns,
|
||||
pathName,
|
||||
router,
|
||||
getShowcaseSectionTitle,
|
||||
};
|
||||
};
|
||||
|
||||
export default useFeedbackTablePresenter;
|
||||
@@ -1,20 +0,0 @@
|
||||
"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",
|
||||
|
||||
"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, FeedbackIcon } from "@/assets/icons";
|
||||
import { ExclamationIcon, EyeIcon } from "@/assets/icons";
|
||||
import Pagination from "@/components/ui/pagination";
|
||||
import TableSkeleton from "@/components/ui/TableSkeleton";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -70,19 +70,19 @@ const TendersTable = () => {
|
||||
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 />
|
||||
<EyeIcon />
|
||||
</button>
|
||||
<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}/feedback/${item.id}`);
|
||||
}}
|
||||
>
|
||||
<FeedbackIcon />
|
||||
<ExclamationIcon />
|
||||
</button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -497,6 +497,7 @@ const CreateCompany = ({ defaultValues, editMode, id }: IProps) => {
|
||||
{...register("tags.cpv_codes")}
|
||||
label="CPV codes"
|
||||
name="tags.cpv_codes"
|
||||
shouldAddWithSpace
|
||||
watch={watch}
|
||||
setValue={setValue}
|
||||
placeholder="Enter Tag CPV codes"
|
||||
|
||||
@@ -6,6 +6,7 @@ import MultiSelect from "@/components/FormElements/MultiSelect";
|
||||
import { Select } from "@/components/FormElements/select";
|
||||
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
|
||||
import FormFooter from "@/components/ui/FormFooter";
|
||||
import { USERNAME_REGEX } from "@/constants/regex";
|
||||
import { FormErrorMessages } from "@/constants/Texts";
|
||||
import { CreateCustomerCredentials } from "@/lib/api";
|
||||
import useCreateCustomerPresenter from "./useCreateCustomerPresenter";
|
||||
@@ -17,7 +18,7 @@ interface IProps {
|
||||
}
|
||||
|
||||
const CreateCustomer = ({ defaultValues, editMode, id }: IProps) => {
|
||||
const { errors, handleSubmit, register, onSubmit, companies, router } =
|
||||
const { errors, handleSubmit, register, watch, onSubmit, companies, router } =
|
||||
useCreateCustomerPresenter({ defaultValues, editMode, id });
|
||||
|
||||
return (
|
||||
@@ -36,7 +37,7 @@ const CreateCustomer = ({ defaultValues, editMode, id }: IProps) => {
|
||||
message: FormErrorMessages.maxLength(30),
|
||||
},
|
||||
pattern: {
|
||||
value: /^[a-zA-Z0-9](?:[a-zA-Z0-9._]*[a-zA-Z0-9])?$/,
|
||||
value: USERNAME_REGEX,
|
||||
message: FormErrorMessages.invalidPattern,
|
||||
},
|
||||
})}
|
||||
@@ -54,6 +55,7 @@ const CreateCustomer = ({ defaultValues, editMode, id }: IProps) => {
|
||||
<div className="flex flex-col gap-2">
|
||||
<MultiSelect
|
||||
label="Select companies"
|
||||
value={watch("companies")}
|
||||
items={
|
||||
companies
|
||||
? companies?.map((item) => ({
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from "@/hooks/queries";
|
||||
import { CreateCustomerCredentials } from "@/lib/api";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
import { SubmitHandler, useForm } from "react-hook-form";
|
||||
|
||||
interface IProps {
|
||||
@@ -24,6 +25,8 @@ const useCreateCustomerPresenter = ({
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
setValue,
|
||||
watch,
|
||||
} = useForm<CreateCustomerCredentials>({
|
||||
mode: "onChange",
|
||||
defaultValues,
|
||||
@@ -34,6 +37,14 @@ const useCreateCustomerPresenter = ({
|
||||
id as string,
|
||||
);
|
||||
const { data: companies } = useCompanyFullList();
|
||||
useEffect(() => {
|
||||
if (defaultValues) {
|
||||
const companies = defaultValues?.companies?.map(
|
||||
(company: any) => company.id,
|
||||
);
|
||||
setValue("companies", companies);
|
||||
}
|
||||
}, [defaultValues]);
|
||||
const onSubmit: SubmitHandler<CreateCustomerCredentials> = (data) => {
|
||||
if (editMode) {
|
||||
updateCustomer({
|
||||
@@ -41,7 +52,6 @@ const useCreateCustomerPresenter = ({
|
||||
credentials: data,
|
||||
});
|
||||
} else {
|
||||
|
||||
createCustomer({
|
||||
...data,
|
||||
employee_count: data.employee_count ? +data.employee_count : undefined,
|
||||
@@ -52,6 +62,7 @@ const useCreateCustomerPresenter = ({
|
||||
};
|
||||
return {
|
||||
handleSubmit,
|
||||
watch,
|
||||
register,
|
||||
errors,
|
||||
onSubmit,
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export const PHONE_REGEX = /^[+\d]+$/;
|
||||
export const USERNAME_REGEX = /^[a-zA-Z0-9](?:[a-zA-Z0-9._]*[a-zA-Z0-9])?$/;
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useRouter } from "next/navigation";
|
||||
import { useMemo } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
export const useLoginQuery = () => {
|
||||
export const useLoginQuery = (callback?: () => void) => {
|
||||
const mutationKey = useMemo(() => [API_ENDPOINTS.USER.LOGIN], []);
|
||||
const { setAuthState } = useUser();
|
||||
const router = useRouter();
|
||||
@@ -18,9 +18,10 @@ export const useLoginQuery = () => {
|
||||
mutationKey,
|
||||
mutationFn: userService.login,
|
||||
onSuccess: (response) => {
|
||||
if (response.success) {
|
||||
setAuthState(response.data);
|
||||
router.replace("/charts");
|
||||
setAuthState(response.data);
|
||||
router.replace("/charts");
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user