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";
|
"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
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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=""
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
+30
-17
@@ -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;
|
|
||||||
@@ -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 +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])?$/;
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user