Files
graff-mate-client/src/components/modals/CreateSessionModal.tsx
T

301 lines
9.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 { Server } from "../../types/Server.ts";
import { useEffect, useRef, useState } from "react";
import { App } from "../../types/App.ts";
import api from "../../utils/api.ts";
import { Session } from "../../types/Session.ts";
import { Client } from "../../types/Client.ts";
import useModalStore from "../../stores/useModalStore.ts";
import TableSelector from "../TableSelector.tsx";
import Input from "../Input.tsx";
import StartSessionIcon from "../icons/StartSessionIcon.tsx";
import Button from "../Button.tsx";
import ProjectSelector from "../ProjectSelector.tsx";
import { useQueryClient, useMutation, useQuery } from "@tanstack/react-query";
import { useDebounce } from "@uidotdev/usehooks";
import { AnimatePresence, motion } from "motion/react";
interface Props {
targetServerId: string | null;
client?: Client | null;
}
export default function CreateSessionModal({ targetServerId, client }: Props) {
const { setModal } = useModalStore();
const [name, setName] = useState<string | null>(client?.name || null);
const [phone, setPhone] = useState<string | null>(client?.phone || null);
const [email, setEmail] = useState<string | null>(client?.email || null);
// const [isSessionExists, setIsSessionExists] = useState(false);
const queryClient = useQueryClient();
const { data: servers } = useQuery({
queryKey: ["servers"],
queryFn: () => api.get("servers?withLastSession=true").json<Server[]>(),
refetchInterval: 1000,
});
const targetServer = targetServerId
? servers?.find((server) => server.id === targetServerId) || null
: null;
const [selectedServer, setSelectedServer] = useState<Server | null>(
targetServer
);
const [selectedApp, setSelectedApp] = useState<App | null>(null);
useEffect(() => {
setSelectedApp(
selectedServer?.sessions?.[0]?.app ||
selectedServer?.apps?.[0].app ||
null
);
}, [selectedServer]);
const debouncedPhone = useDebounce(phone, 500);
const { data, isLoading, error } = useQuery({
queryKey: ["get-user-by-phone", debouncedPhone],
queryFn: () =>
api
.get("clients/by-phone", {
searchParams: debouncedPhone ? { phone: debouncedPhone } : {},
})
.json<Client>(),
enabled: !!debouncedPhone,
});
useEffect(() => {
if (!error && data) {
setName(data.name);
setEmail(data.email);
} else {
setName(null);
setEmail(null);
}
}, [data, error]);
const { mutate: createClient } = useMutation({
mutationFn: () =>
api
.post("clients", {
json: {
name,
phone,
email,
},
})
.json<Client>(),
});
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<Session>(),
onMutate: () => {
queryClient.invalidateQueries({ queryKey: ["sessions"] });
queryClient.invalidateQueries({ queryKey: ["servers"] });
setModal(null);
},
});
// const { mutate: endSession } = useMutation({
// mutationKey: ["end-session", 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, {
// onError: (error) => {
// console.log("Ошибка при завершении сессии:", error);
// },
// });
}
// useEffect(() => {
// if (
// selectedServer &&
// servers?.find((server) => server.id === selectedServer?.id)?.sessions?.[0]
// ?.status === "ended" &&
// selectedApp
// // && isSessionExists
// )
// createClient(undefined, {
// onSuccess: (client) => {
// createSession({
// clientId: client.id,
// serverId: selectedServer?.id,
// appId: selectedApp.id,
// });
// },
// });
// }, [
// selectedApp,
// servers,
// createClient,
// createSession,
// // isSessionExists,
// selectedServer,
// ]);
const ref = useRef<HTMLFormElement>(null);
return (
<form
className="relative rounded-[2.222vw] w-[25vw] 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-1moverflow-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]">
<Input
value={phone || ""}
onChange={(e) => setPhone(e.target.value)}
placeholder="Номер телефона"
required
isLoading={isLoading}
/>
<AnimatePresence>
{phone && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<Input
value={name || ""}
disabled={isLoading}
onChange={(e) => setName(e.target.value)}
placeholder="Имя"
required
/>
</motion.div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<Input
disabled={isLoading}
type="email"
value={email || ""}
onChange={(e) => setEmail(e.target.value)}
placeholder="Электронная почта"
/>
</motion.div>
</>
)}
</AnimatePresence>
</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.map(({ app }) => app)}
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> */}
{/* )} */}
<div className="flex-1 flex flex-col justify-end">
<Button
type="submit"
disabled={
!phone ||
!name ||
!selectedServer ||
!selectedApp ||
servers?.find((server) => server.id === selectedServer?.id)
?.sessions?.[0]?.status === "ending"
}
variant="cta"
size="large"
>
<div className="size-[1.111vw] text-[#9184F6]">
<StartSessionIcon />
</div>
<span>Запустить сеанс</span>
</Button>
</div>
</div>
</form>
);
}