220 lines
8.9 KiB
TypeScript
220 lines
8.9 KiB
TypeScript
import { format, isToday } from "date-fns";
|
||
import { Client } from "../../types/Client";
|
||
import Badge from "../Badge";
|
||
import Button from "../Button";
|
||
import PeopleIcon from "../icons/PeopleIcon";
|
||
import PlusIcon from "../icons/PlusIcon";
|
||
import Input from "../Input";
|
||
import { ru } from "date-fns/locale";
|
||
import ChevronRightIcon from "../icons/ChevronRightIcon";
|
||
import CopyIcon from "../icons/CopyIcon";
|
||
import { useState } from "react";
|
||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||
import api from "../../utils/api";
|
||
import SpinIcon from "../icons/SpinIcon";
|
||
import useModalStore from "../../stores/useModalStore";
|
||
import CreateSessionModal from "./CreateSessionModal";
|
||
import SessionModal from "./SessionModal";
|
||
|
||
function ClientModal({ client }: { client: Client }) {
|
||
const queryClient = useQueryClient();
|
||
|
||
const { setModal } = useModalStore();
|
||
|
||
const [clientData, setClientData] = useState(client);
|
||
|
||
const { mutate: updateClientData, isPending } = useMutation({
|
||
mutationKey: ["clients", client.id],
|
||
mutationFn: () =>
|
||
api.put(`clients/${client.id}`, {
|
||
json: {
|
||
name: clientData.name,
|
||
phone: clientData.phone.replace(/\D/g, ""),
|
||
email: clientData.email,
|
||
},
|
||
}),
|
||
onSuccess: () => {
|
||
queryClient.invalidateQueries({ queryKey: ["clients"] });
|
||
},
|
||
});
|
||
|
||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||
e.preventDefault();
|
||
updateClientData();
|
||
};
|
||
|
||
return (
|
||
<div className="w-[49.722vw] bg-[#FFFFFF] rounded-[2.222vw] overflow-hidden">
|
||
<div className="flex justify-center items-center py-[1.806vw] border-b border-[#D6D6D6]">
|
||
<p className="title-s font-medium">{client.name}</p>
|
||
</div>
|
||
<div className="flex bg-[#F0F0F0] h-[calc(100vh-8.861vw)] rounded-b-[2.222vw]">
|
||
<div className="flex flex-col gap-[1.111vw] p-[1.111vw] flex-1 overflow-y-auto [scrollbar-width:thin]">
|
||
<div className="flex flex-col gap-[1.111vw] rounded-[1.667vw] bg-white p-[1.111vw]">
|
||
<div className="flex flex-col gap-[0.278vw]">
|
||
<p className="title-s font-medium">Данные клиента</p>
|
||
<p className="caption-s text-[#BDBDBD] font-medium">
|
||
Вы можете изменить данные клиента
|
||
</p>
|
||
</div>
|
||
<form onSubmit={handleSubmit}>
|
||
<div className="flex flex-col gap-[0.278vw]">
|
||
<Input
|
||
placeholder="Имя"
|
||
defaultValue={clientData.name || ""}
|
||
onChange={(e) => {
|
||
setClientData({ ...clientData, name: e.target.value });
|
||
}}
|
||
required
|
||
>
|
||
<span
|
||
className="absolute z-10 top-[1.25vw] left-[17.917vw] size-[1.389vw] text-[#7D7D7D] cursor-pointer"
|
||
onClick={() => {
|
||
navigator.clipboard.writeText(clientData.name);
|
||
}}
|
||
>
|
||
<CopyIcon />
|
||
</span>
|
||
</Input>
|
||
<Input
|
||
placeholder="Номер телефона"
|
||
defaultValue={clientData.phone || ""}
|
||
onChange={(e) => {
|
||
setClientData({ ...clientData, phone: e.target.value });
|
||
}}
|
||
required
|
||
mask="+7 (999) 999-99-99"
|
||
>
|
||
<span
|
||
className="absolute top-[1.25vw] left-[17.917vw] size-[1.389vw] text-[#7D7D7D] cursor-pointer"
|
||
onClick={() => {
|
||
navigator.clipboard.writeText(clientData.phone);
|
||
}}
|
||
>
|
||
<CopyIcon />
|
||
</span>
|
||
</Input>
|
||
<Input
|
||
placeholder="Эл. почта"
|
||
defaultValue={clientData.email || ""}
|
||
onChange={(e) => {
|
||
setClientData({ ...clientData, email: e.target.value });
|
||
}}
|
||
/>
|
||
</div>
|
||
<div className="mt-[1.111vw]">
|
||
<Button
|
||
variant="primary"
|
||
size="large"
|
||
className="w-full"
|
||
type="submit"
|
||
disabled={
|
||
!clientData.name ||
|
||
!clientData.phone ||
|
||
isPending ||
|
||
(clientData.name === client.name &&
|
||
clientData.phone.replace(/\D/g, "") ===
|
||
client.phone.replace(/\D/g, "") &&
|
||
clientData.email === client.email)
|
||
}
|
||
>
|
||
{isPending ? (
|
||
<span className="size-[1.111vw] animate-spin text-[#7B60F3] flex items-center justify-center">
|
||
<SpinIcon />
|
||
</span>
|
||
) : (
|
||
"Сохранить изменения"
|
||
)}
|
||
</Button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div className="flex flex-col gap-[1.111vw] rounded-[1.667vw] bg-white p-[1.111vw]">
|
||
<div className="flex flex-col gap-[0.278vw]">
|
||
<p className="title-s font-medium">Управление доступом</p>
|
||
<p className="caption-s text-[#BDBDBD] font-medium">
|
||
Выберите, кто может видеть и редактировать данные этого клиента
|
||
</p>
|
||
</div>
|
||
<div className="flex gap-[0.556vw]">
|
||
<div className="size-[2.222vw] rounded-full bg-[#F0F0F0] bg-[url(/images/mock_manager_photo_c.png)] bg-cover bg-no-repeat bg-center" />
|
||
<div className="size-[2.222vw] rounded-full bg-[#F0F0F0] bg-[url(/images/mock_manager_photo_1_c.png)] bg-cover bg-no-repeat bg-center" />
|
||
</div>
|
||
<div>
|
||
<button className="button-m text-[#7B60F3] font-medium flex items-center gap-[0.278vw]">
|
||
<span className="size-[0.972vw]">
|
||
<PeopleIcon />
|
||
</span>
|
||
Настроить доступ
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div className="flex flex-col gap-[1.111vw] rounded-[1.667vw] bg-white p-[1.111vw]">
|
||
<div className="flex items-center gap-[0.556vw]">
|
||
<p className="title-s font-medium">История сеансов</p>
|
||
{client.sessions.length > 0 && (
|
||
<Badge count={client.sessions.length} />
|
||
)}
|
||
</div>
|
||
<div className="flex flex-col gap-[0.556vw]">
|
||
{client.sessions.length === 0 && (
|
||
<p className="caption-s text-[#BDBDBD] font-medium text-center">
|
||
Пока не было сеансов
|
||
</p>
|
||
)}
|
||
{client.sessions.map((session) => (
|
||
<div
|
||
key={session.id}
|
||
className="p-[0.278vw] border-b border-[#F6F6F6] cursor-pointer"
|
||
onClick={() => {
|
||
setModal(<SessionModal session={session} />);
|
||
}}
|
||
>
|
||
<div className="p-[0.833vw] flex justify-between items-center">
|
||
<div className="flex gap-[0.556vw] items-center">
|
||
<div className="size-[2.5vw] rounded-full bg-[#F0F0F0] bg-[url(/images/mock_manager_photo_c.png)] bg-cover bg-no-repeat bg-center" />
|
||
<div className="flex flex-col gap-[0.278vw]">
|
||
<p className="button-m font-medium">
|
||
{session.manager.fullname}
|
||
</p>
|
||
<p className="caption-s text-[#BDBDBD] font-medium">
|
||
{isToday(new Date(session.updatedAt))
|
||
? "Сегодня"
|
||
: format(new Date(session.updatedAt), "d MMMM", {
|
||
locale: ru,
|
||
})}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<span className="size-[1.389vw] text-[#7D7D7D]">
|
||
<ChevronRightIcon />
|
||
</span>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
<Button
|
||
variant="cta"
|
||
size="large"
|
||
className="w-full"
|
||
onClick={() =>
|
||
setModal(
|
||
<CreateSessionModal targetServerId={null} client={client} />
|
||
)
|
||
}
|
||
>
|
||
<span className="size-[1.111vw]">
|
||
<PlusIcon />
|
||
</span>
|
||
Новый сеанс с клиентом
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
<div className="flex-1"></div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default ClientModal;
|