Files
graff-mate-client/src/components/modals/CreateSessionModal.tsx
T
2025-06-05 16:46:49 +05:00

237 lines
7.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { IServer } from "../../types/IServer.ts";
import { useEffect, useRef, useState } from "react";
import { IApp } from "../../types/IApp.ts";
import api from "../../utils/api.ts";
import { ISession } from "../../types/ISession.ts";
import { IClient } from "../../types/IClient.ts";
import useModalStore from "../../stores/useModalStore.ts";
import TableSelector from "../TableSelector.tsx";
import NewInput from "../NewInput.tsx";
import StartSessionIcon from "../icons/StartSessionIcon.tsx";
import NewButton from "../NewButton.tsx";
import ProjectSelector from "../ProjectSelector.tsx";
import { useQueryClient, useMutation, useQuery } from "@tanstack/react-query";
interface Props {
targetServerId: string | null;
}
export default function CreateSessionModal({ targetServerId }: Props) {
const { setModal } = useModalStore();
const [name, setName] = useState("");
const [phone, setPhone] = useState("");
const [email, setEmail] = useState("");
const [isSessionExists, setIsSessionExists] = useState(false);
const queryClient = useQueryClient();
const { data: servers } = useQuery({
queryKey: ["servers"],
queryFn: () => api.get("servers?withLastSession=true").json<IServer[]>(),
refetchInterval: 1000,
});
const targetServer = targetServerId
? servers?.find((server) => server.id === targetServerId) || null
: null;
const [selectedServer, setSelectedServer] = useState<IServer | null>(
targetServer
);
const [selectedApp, setSelectedApp] = useState<IApp | null>(null);
useEffect(() => {
setSelectedApp(
selectedServer?.sessions?.[0]?.app || selectedServer?.apps?.[0] || null
);
}, [selectedServer]);
const { mutate: createClient } = useMutation({
mutationFn: () => {
return api
.post("clients", {
json: {
name,
phone,
email,
},
})
.json<IClient>();
},
});
const { mutate: createSession } = useMutation({
mutationKey: ["create-session", selectedServer?.id],
mutationFn: ({
clientId,
serverId,
appId,
}: {
clientId: string;
serverId: string;
appId: string;
}) =>
api
.post("sessions", {
json: {
clientId,
serverId,
appId,
},
})
.json<ISession>(),
onMutate: () => {
queryClient.invalidateQueries({ queryKey: ["sessions"] });
queryClient.invalidateQueries({ queryKey: ["servers"] });
setModal(null);
},
});
const { mutate: endSession } = useMutation({
mutationKey: ["sessions", selectedServer?.sessions?.[0]?.id],
mutationFn: () =>
api.put(`sessions/${selectedServer?.sessions?.[0]?.id}`, {
json: { status: "ending" },
}),
onMutate: () => queryClient.invalidateQueries({ queryKey: ["sessions"] }),
});
async function handleClickCreateSession(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
if (!name || !phone || !selectedServer || !selectedApp) return;
if (selectedServer?.sessions?.[0]?.status !== "started") {
createClient(undefined, {
onSuccess: (client) => {
createSession({
clientId: client.id,
serverId: selectedServer.id,
appId: selectedApp.id,
});
},
});
return;
}
if (!isSessionExists) {
setIsSessionExists(true);
return;
}
endSession(undefined, {
onSuccess: () => {
createClient(undefined, {
onSuccess: (client) => {
createSession({
clientId: client.id,
serverId: selectedServer.id,
appId: selectedApp.id,
});
},
onError: (error) => {
console.log(error);
},
});
},
onError: (error) => {
console.log("Ошибка при завершении сессии:", error);
},
});
}
const ref = useRef<HTMLFormElement>(null);
return (
<form
className="relative rounded-[2.222vw] w-[25vw] min-h-[calc(100dvh-2.222vw)] bg-[#F0F0F0] flex flex-col overflow-hidden"
onSubmit={handleClickCreateSession}
ref={ref}
>
<div className="w-full h-[4.861vw] flex items-center justify-center">
<p className="title-s font-medium">Новый сеанс</p>
</div>
<div className="w-full h-[6.944vw] bg-[url(/images/Table.png)] bg-no-repeat bg-top bg-[length:9.306vw]" />
<div className="bg-white rounded-t-[2.222vw] p-[1.389vw] flex flex-col gap-y-[1.667vw] flex-1 overflow-y-auto">
<TableSelector
tables={servers || []}
selectedTable={selectedServer}
onSelect={setSelectedServer}
/>
<div className="flex flex-col gap-y-[0.833vw]">
<p className="title-s font-medium">Укажите данные клиента</p>
<div className="flex flex-col gap-y-[0.556vw]">
<NewInput
value={phone}
onChange={(e) => setPhone(e.target.value)}
placeholder="Номер телефона"
required
/>
<NewInput
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Имя"
required
/>
<NewInput
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Электронная почта"
/>
</div>
</div>
<div className="flex flex-col gap-y-[0.833vw]">
<p className="title-s font-medium">Выберите параметры сеанса</p>
{selectedServer &&
selectedServer?.apps &&
selectedServer?.apps?.length > 0 && (
<ProjectSelector
activeProject={
selectedServer?.sessions?.[0]?.status === "started"
? selectedApp
: null
}
projects={selectedServer?.apps}
selectedProject={selectedApp}
setSelectedProject={setSelectedApp}
/>
)}
</div>
{isSessionExists && (
<div className="absolute inset-0 top-[11.806vw] bg-[#FFFFFF] flex flex-col gap-[1.111vw] items-center justify-center h-[31.458vw]">
<img
src="/images/ghost.png"
alt="ghost error"
className="w-[13.889vw] h-[11.806vw]"
/>
<div className="flex flex-col gap-[0.556vw] items-center">
<h3 className="title-m font-medium">Есть текущий сеанс</h3>
<p className="caption-s text-[#BDBDBD] text-center whitespace-pre-line">
{`На выбранном столе есть текущий сеанс.
При запуске нового текущий будет завершен.`}
</p>
</div>
</div>
)}
<NewButton
type="submit"
disabled={
!ref.current?.checkValidity() || !selectedServer || !selectedApp
}
variant="cta"
size="large"
className="sticky bottom-[1.111vw]"
>
<div className="rounded-full bg-[#9184F6] in-disabled:bg-transparent px-[0.278vw] py-[0.208vw] size-[1.111vw]">
<div className="w-[0.694vw] h-[0.556vw]">
<StartSessionIcon />
</div>
</div>
<p>Запустить сеанс</p>
</NewButton>
</div>
</form>
);
}