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:
Nima Nakhsotin
2025-09-24 11:57:15 +03:30
21 changed files with 255 additions and 121 deletions
@@ -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;
+29
View File
@@ -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;
+3 -2
View File
@@ -1,6 +1,7 @@
"use client"; "use client";
import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb"; 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 { BreadcrumbItem } from "@/types/shared";
import { use } from "react"; import { use } from "react";
interface IProps { interface IProps {
@@ -20,7 +21,7 @@ const TenderFeedbackPage = ({ params }: IProps) => {
return ( return (
<> <>
<Breadcrumb items={breadcrumbItems} /> <Breadcrumb items={breadcrumbItems} />
<TenderFeedbackTable id={id} /> <FeedbackTable id={id} paramKey="tender_id" />
</> </>
); );
}; };
+15 -15
View File
@@ -214,7 +214,20 @@ export function GlobeIcon(props: IconProps) {
</svg> </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) { export function UserSettingIcon(props: IconProps) {
return ( return (
<svg <svg
@@ -297,20 +310,7 @@ export function EmailIcon(props: IconProps) {
</svg> </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) { export function PasswordIcon(props: IconProps) {
return ( return (
<svg <svg
+14 -7
View File
@@ -1,30 +1,32 @@
"use client"; "use client";
import { PasswordIcon, UserIcon } from "@/assets/icons"; 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 { 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 { FormErrorMessages } from "@/constants/Texts";
import { useIsMutating } from "@tanstack/react-query";
export default function SigninWithPassword() { export default function SigninWithPassword() {
const isMutating = useIsMutating();
const [passwordFieldInputType, setPasswordFieldInputType] = useState< const [passwordFieldInputType, setPasswordFieldInputType] = useState<
"password" | "text" "password" | "text"
>("password"); >("password");
const { handleSubmit, control, reset, register } = const { handleSubmit, control, reset, register } =
useForm<ILoginCredentials>(); useForm<ILoginCredentials>();
const { mutate, isPending } = useLoginQuery(); const { mutate } = useLoginQuery(reset);
const onSubmit: SubmitHandler<ILoginCredentials> = (data) => { const onSubmit: SubmitHandler<ILoginCredentials> = (data) => {
mutate(data); mutate(data);
reset();
}; };
return ( return (
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<InputGroup <InputGroup
type="username" type="username"
disabled={!!isMutating}
{...register("username", { {...register("username", {
required: { required: {
value: true, value: true,
@@ -40,6 +42,7 @@ export default function SigninWithPassword() {
<InputGroup <InputGroup
type={passwordFieldInputType} type={passwordFieldInputType}
disabled={!!isMutating}
{...register("password", { {...register("password", {
required: { required: {
value: true, value: true,
@@ -67,7 +70,11 @@ export default function SigninWithPassword() {
type="submit" 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" 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> </button>
</div> </div>
</form> </form>
@@ -28,6 +28,7 @@ type TagInputProps<T extends FieldValues> = {
watch?: UseFormWatch<T>; watch?: UseFormWatch<T>;
errors?: FieldErrors<T>; errors?: FieldErrors<T>;
maxTags?: number; maxTags?: number;
shouldAddWithSpace?: boolean;
allowDuplicates?: boolean; allowDuplicates?: boolean;
tagValidator?: (tag: string) => boolean; tagValidator?: (tag: string) => boolean;
} & Partial<UseFormRegisterReturn>; } & Partial<UseFormRegisterReturn>;
@@ -43,6 +44,7 @@ const TagInput = <T extends FieldValues>({
name, name,
register, register,
setValue, setValue,
shouldAddWithSpace = false,
watch, watch,
errors, errors,
maxTags, maxTags,
@@ -87,7 +89,11 @@ const TagInput = <T extends FieldValues>({
}; };
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => { const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" || e.key === ",") { if (
e.key === "Enter" ||
e.key === "," ||
(shouldAddWithSpace && e.key === " ")
) {
e.preventDefault(); e.preventDefault();
addTag(inputValue); addTag(inputValue);
} else if (e.key === "Backspace" && !inputValue && tags.length > 0) { } else if (e.key === "Backspace" && !inputValue && tags.length > 0) {
@@ -167,7 +173,7 @@ const TagInput = <T extends FieldValues>({
<input <input
type="hidden" type="hidden"
{...(register ? register(name) : {})} {...(register ? register(name) : {})}
value={tags.length > 0 ? JSON.stringify(tags) : ''} value={tags.length > 0 ? JSON.stringify(tags) : ""}
ref={ref} ref={ref}
/> />
</div> </div>
+1 -1
View File
@@ -24,7 +24,7 @@ export function Header() {
{isMobile && ( {isMobile && (
<Link href={"/"} className="ml-2 max-[430px]:hidden min-[375px]:ml-4"> <Link href={"/"} className="ml-2 max-[430px]:hidden min-[375px]:ml-4">
<Image <Image
src={"/images/logo/logo-icon.svg"} src={"/images/fav-icon.svg"}
width={32} width={32}
height={32} height={32}
alt="" alt=""
+27 -25
View File
@@ -1,5 +1,5 @@
"use client"; "use client";
import { PencilSquareIcon, TrashIcon } from "@/assets/icons"; import { ExclamationIcon, PencilSquareIcon, TrashIcon } from "@/assets/icons";
import ConfirmationModal from "@/components/ui/ConfirmationModal"; import ConfirmationModal from "@/components/ui/ConfirmationModal";
import { import {
Table, Table,
@@ -58,39 +58,33 @@ const CompaniesTable = ({}: IProps) => {
key={company.id} key={company.id}
className="odd:bg-gray-2 dark:odd:bg-gray-7" className="odd:bg-gray-2 dark:odd:bg-gray-7"
> >
<TableHead className="text-start" colSpan={100}> <TableCell className="text-start" colSpan={100}>
{index + 1} {index + 1}
</TableHead> </TableCell>
<TableHead className="text-start" colSpan={100}> <TableCell className="text-start" colSpan={100}>
{company.name} {company.name}
</TableHead> </TableCell>
<TableHead className="text-start" colSpan={100}> <TableCell className="text-start" colSpan={100}>
{company.email} {company.email}
</TableHead> </TableCell>
<TableHead className="w-full text-start" colSpan={100}> <TableCell className="w-full text-start" colSpan={100}>
{formatPhoneNumber(company.phone)} {formatPhoneNumber(company.phone)}
</TableHead> </TableCell>
<TableHead className="text-start" colSpan={100}> <TableCell className="text-start" colSpan={100}>
{company.address.country} {company.address.country}
</TableHead> </TableCell>
<TableHead className="text-start" colSpan={100}> <TableCell className="text-start" colSpan={100}>
{company.address.state} {company.address.state}
</TableHead> </TableCell>
<TableHead className="text-start" colSpan={100}> <TableCell 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}>
{company.language} {company.language}
</TableHead> </TableCell>
<TableHead className="text-start" colSpan={100}> <TableCell className="text-start" colSpan={100}>
{company.currency} {company.currency}
</TableHead> </TableCell>
<TableHead className="text-start" colSpan={100}> <TableCell className="text-start" colSpan={100}>
{company.employee_count} {company.employee_count}
</TableHead> </TableCell>
<TableCell className="text-start xl:pr-7.5"> <TableCell className="text-start xl:pr-7.5">
<div className="flex items-center justify-start gap-x-3.5"> <div className="flex items-center justify-start gap-x-3.5">
<button <button
@@ -112,6 +106,14 @@ const CompaniesTable = ({}: IProps) => {
<span className="sr-only">Edit Company </span> <span className="sr-only">Edit Company </span>
<PencilSquareIcon /> <PencilSquareIcon />
</button> </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> </div>
</TableCell> </TableCell>
</TableRow> </TableRow>
@@ -31,8 +31,7 @@ export const useCompanyListPresenter = () => {
"phone", "phone",
"country", "country",
"state", "state",
"city",
"postal code",
"language", "language",
"currency", "currency",
"employee count", "employee count",
@@ -1,31 +1,31 @@
import { import { Dispatch, FC, SetStateAction, useEffect, useState } from "react";
Dispatch,
FC,
RefObject,
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 { Select } from "@/components/FormElements/select";
import { Building } from "@/components/Layouts/sidebar/icons"; import { Building } from "@/components/Layouts/sidebar/icons";
import { FormErrorMessages } from "@/constants/Texts"; 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 { interface IProps {
setCompanies: Dispatch< 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 [options, setOptions] = useState<ILabelValue[]>([]);
const { data, status, isSuccess } = useCompanyFullList(); const { data, status, isSuccess } = useCompanyFullList();
const { handleSubmit, register } = const { handleSubmit, register } =
useForm<TAssignCustomerToCompanyCredentials>(); useForm<TAssignCustomerToCompanyCredentials>({
mode: "onChange",
defaultValues,
});
useEffect(() => { useEffect(() => {
if (isSuccess) { if (isSuccess) {
+16 -2
View File
@@ -1,5 +1,5 @@
"use client"; "use client";
import { PencilSquareIcon, TrashIcon } from "@/assets/icons"; import { ExclamationIcon, PencilSquareIcon, TrashIcon } from "@/assets/icons";
import ConfirmationModal from "@/components/ui/ConfirmationModal"; import ConfirmationModal from "@/components/ui/ConfirmationModal";
import { import {
Table, Table,
@@ -112,11 +112,22 @@ const CustomersTable = () => {
onClick={() => { onClick={() => {
setCurrentCustomer(customer); setCurrentCustomer(customer);
setIsAssignCompanyModalOpen(true); setIsAssignCompanyModalOpen(true);
setSelectedCompanies({
company_ids: [customer?.companies?.[0]?.id ?? ""],
});
}} }}
> >
<span className="sr-only">Assign To Company </span> <span className="sr-only">Assign To Company </span>
<Building /> <Building />
</button> </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> </div>
</TableCell> </TableCell>
</TableRow> </TableRow>
@@ -137,7 +148,10 @@ const CustomersTable = () => {
isOpen={isAssignCompanyModalOpen} isOpen={isAssignCompanyModalOpen}
onClose={() => setIsAssignCompanyModalOpen(false)} onClose={() => setIsAssignCompanyModalOpen(false)}
> >
<AssignToCompanyModalContent setCompanies={setSelectedCompanies} /> <AssignToCompanyModalContent
setCompanies={setSelectedCompanies}
defaultValues={selectedCompanies}
/>
</Modal> </Modal>
{isModalOpen && ( {isModalOpen && (
<ConfirmationModal <ConfirmationModal
@@ -12,8 +12,9 @@ import { useState } from "react";
const useCustomerListPresenter = () => { const useCustomerListPresenter = () => {
const [isAssignCompanyModalOpen, setIsAssignCompanyModalOpen] = const [isAssignCompanyModalOpen, setIsAssignCompanyModalOpen] =
useState(false); useState(false);
const [selectedCompanies, setSelectedCompanies] = const [selectedCompanies, setSelectedCompanies] = useState<
useState<TAssignCustomerToCompanyCredentials | null>(null); TAssignCustomerToCompanyCredentials | undefined
>();
const [currentCustomer, setCurrentCustomer] = useState<TCustomer | null>( const [currentCustomer, setCurrentCustomer] = useState<TCustomer | null>(
null, null,
); );
@@ -50,6 +51,7 @@ const useCustomerListPresenter = () => {
deleteCustomer(currentCustomer.id); deleteCustomer(currentCustomer.id);
} }
}; };
const customers = data?.pages.flatMap((page) => page.data.customers) ?? []; const customers = data?.pages.flatMap((page) => page.data.customers) ?? [];
return { return {
allCustomers: customers, allCustomers: customers,
@@ -1,31 +1,39 @@
"use client";
import { ExclamationIcon } from "@/assets/icons"; import { ExclamationIcon } from "@/assets/icons";
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import { import {
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
TableHead, TableHead,
TableHeader, TableHeader,
TableRow,
} from "@/components/ui/table"; } from "@/components/ui/table";
import TableSkeleton from "@/components/ui/TableSkeleton"; import TableSkeleton from "@/components/ui/TableSkeleton";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { unixToDate } from "@/utils/shared"; import { capitalize, unixToDate } from "@/utils/shared";
import { TableRow } from "../../../ui/table";
import useTenderFeedbackPresenter from "./useTenderFeedbackPresenter";
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import useFeedbackTablePresenter from "./useFeedbackTablePresenter";
interface IProps { interface IProps {
id: string; 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 ( return (
<ShowcaseSection <ShowcaseSection
title={!isLoading && `Tender: ${feedback?.[0].tender?.title}`} 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" !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> <Table>
<TableHeader> <TableHeader>
@@ -38,11 +46,16 @@ const TenderFeedbackTable = ({ id }: IProps) => {
</TableRow> </TableRow>
</TableHeader> </TableHeader>
{isLoading && <TableSkeleton column={columns.length} />} {isLoading && <TableSkeleton column={columns.length} />}
<TableBody> <TableBody>
{feedback?.map((item, index) => {!feedback?.length ? (
!item ? ( <TableRow className="w-full">
<h6 key={index}>No feedback is submitted for this tender</h6> <TableCell colSpan={500} className="text-center">
) : ( <h2>No feedback</h2>
</TableCell>
</TableRow>
) : (
feedback?.map((item, index) => (
<TableRow <TableRow
key={item.id} key={item.id}
className="odd:bg-gray-2 dark:odd:bg-gray-7" className="odd:bg-gray-2 dark:odd:bg-gray-7"
@@ -74,7 +87,7 @@ const TenderFeedbackTable = ({ id }: IProps) => {
</button> </button>
</TableCell> </TableCell>
</TableRow> </TableRow>
), ))
)} )}
</TableBody> </TableBody>
</Table> </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;
+4 -4
View File
@@ -11,7 +11,7 @@ import {
} from "@/components/ui/table"; } from "@/components/ui/table";
import useTenderListPresenter from "./useTenderListPresenter"; import useTenderListPresenter from "./useTenderListPresenter";
import { ExclamationIcon, FeedbackIcon } from "@/assets/icons"; import { ExclamationIcon, EyeIcon } from "@/assets/icons";
import Pagination from "@/components/ui/pagination"; import Pagination from "@/components/ui/pagination";
import TableSkeleton from "@/components/ui/TableSkeleton"; import TableSkeleton from "@/components/ui/TableSkeleton";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -70,19 +70,19 @@ const TendersTable = () => {
colSpan={100} colSpan={100}
> >
<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={() => { onClick={() => {
router.push(`${pathName}/${item.id}`); router.push(`${pathName}/${item.id}`);
}} }}
> >
<ExclamationIcon /> <EyeIcon />
</button> </button>
<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={() => { onClick={() => {
router.push(`${pathName}/feedback/${item.id}`); router.push(`${pathName}/feedback/${item.id}`);
}} }}
> >
<FeedbackIcon /> <ExclamationIcon />
</button> </button>
</TableCell> </TableCell>
</TableRow> </TableRow>
@@ -497,6 +497,7 @@ const CreateCompany = ({ defaultValues, editMode, id }: IProps) => {
{...register("tags.cpv_codes")} {...register("tags.cpv_codes")}
label="CPV codes" label="CPV codes"
name="tags.cpv_codes" name="tags.cpv_codes"
shouldAddWithSpace
watch={watch} watch={watch}
setValue={setValue} setValue={setValue}
placeholder="Enter Tag CPV codes" placeholder="Enter Tag CPV codes"
@@ -6,6 +6,7 @@ import MultiSelect from "@/components/FormElements/MultiSelect";
import { Select } from "@/components/FormElements/select"; import { Select } from "@/components/FormElements/select";
import { ShowcaseSection } from "@/components/Layouts/showcase-section"; import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import FormFooter from "@/components/ui/FormFooter"; import FormFooter from "@/components/ui/FormFooter";
import { USERNAME_REGEX } from "@/constants/regex";
import { FormErrorMessages } from "@/constants/Texts"; import { FormErrorMessages } from "@/constants/Texts";
import { CreateCustomerCredentials } from "@/lib/api"; import { CreateCustomerCredentials } from "@/lib/api";
import useCreateCustomerPresenter from "./useCreateCustomerPresenter"; import useCreateCustomerPresenter from "./useCreateCustomerPresenter";
@@ -17,7 +18,7 @@ interface IProps {
} }
const CreateCustomer = ({ defaultValues, editMode, id }: 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 }); useCreateCustomerPresenter({ defaultValues, editMode, id });
return ( return (
@@ -36,7 +37,7 @@ const CreateCustomer = ({ defaultValues, editMode, id }: IProps) => {
message: FormErrorMessages.maxLength(30), message: FormErrorMessages.maxLength(30),
}, },
pattern: { pattern: {
value: /^[a-zA-Z0-9](?:[a-zA-Z0-9._]*[a-zA-Z0-9])?$/, value: USERNAME_REGEX,
message: FormErrorMessages.invalidPattern, message: FormErrorMessages.invalidPattern,
}, },
})} })}
@@ -54,6 +55,7 @@ const CreateCustomer = ({ defaultValues, editMode, id }: IProps) => {
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<MultiSelect <MultiSelect
label="Select companies" label="Select companies"
value={watch("companies")}
items={ items={
companies companies
? companies?.map((item) => ({ ? companies?.map((item) => ({
@@ -7,6 +7,7 @@ import {
} from "@/hooks/queries"; } from "@/hooks/queries";
import { CreateCustomerCredentials } from "@/lib/api"; import { CreateCustomerCredentials } from "@/lib/api";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useEffect } from "react";
import { SubmitHandler, useForm } from "react-hook-form"; import { SubmitHandler, useForm } from "react-hook-form";
interface IProps { interface IProps {
@@ -24,6 +25,8 @@ const useCreateCustomerPresenter = ({
register, register,
handleSubmit, handleSubmit,
formState: { errors }, formState: { errors },
setValue,
watch,
} = useForm<CreateCustomerCredentials>({ } = useForm<CreateCustomerCredentials>({
mode: "onChange", mode: "onChange",
defaultValues, defaultValues,
@@ -34,6 +37,14 @@ const useCreateCustomerPresenter = ({
id as string, id as string,
); );
const { data: companies } = useCompanyFullList(); 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) => { const onSubmit: SubmitHandler<CreateCustomerCredentials> = (data) => {
if (editMode) { if (editMode) {
updateCustomer({ updateCustomer({
@@ -41,7 +52,6 @@ const useCreateCustomerPresenter = ({
credentials: data, credentials: data,
}); });
} else { } else {
createCustomer({ createCustomer({
...data, ...data,
employee_count: data.employee_count ? +data.employee_count : undefined, employee_count: data.employee_count ? +data.employee_count : undefined,
@@ -52,6 +62,7 @@ const useCreateCustomerPresenter = ({
}; };
return { return {
handleSubmit, handleSubmit,
watch,
register, register,
errors, errors,
onSubmit, onSubmit,
+1
View File
@@ -1 +1,2 @@
export const PHONE_REGEX = /^[+\d]+$/; export const PHONE_REGEX = /^[+\d]+$/;
export const USERNAME_REGEX = /^[a-zA-Z0-9](?:[a-zA-Z0-9._]*[a-zA-Z0-9])?$/;
+5 -4
View File
@@ -10,7 +10,7 @@ import { useRouter } from "next/navigation";
import { useMemo } from "react"; import { useMemo } from "react";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
export const useLoginQuery = () => { export const useLoginQuery = (callback?: () => void) => {
const mutationKey = useMemo(() => [API_ENDPOINTS.USER.LOGIN], []); const mutationKey = useMemo(() => [API_ENDPOINTS.USER.LOGIN], []);
const { setAuthState } = useUser(); const { setAuthState } = useUser();
const router = useRouter(); const router = useRouter();
@@ -18,9 +18,10 @@ export const useLoginQuery = () => {
mutationKey, mutationKey,
mutationFn: userService.login, mutationFn: userService.login,
onSuccess: (response) => { onSuccess: (response) => {
if (response.success) { setAuthState(response.data);
setAuthState(response.data); router.replace("/charts");
router.replace("/charts"); if (callback) {
callback();
} }
}, },
}); });