This commit is contained in:
2025-06-05 16:46:49 +05:00
parent 4091626bc8
commit 2be9e0dc2b
5 changed files with 128 additions and 108 deletions
+18 -3
View File
@@ -5,6 +5,8 @@ import api from "../utils/api";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import ChevronRightIcon from "./icons/ChevronRightIcon";
import { motion } from "motion/react";
import CurrentSessionModal from "./modals/CurrentSessionModal";
import useModalStore from "../stores/useModalStore";
function CurrentSessionCard({
session,
@@ -13,9 +15,11 @@ function CurrentSessionCard({
session: ISession;
index: number;
}) {
const { setModal } = useModalStore();
const queryClient = useQueryClient();
const { mutate: endSession } = useMutation({
const { mutate: endSession, isPending } = useMutation({
mutationKey: ["sessions", session.id],
mutationFn: () =>
api.put(`sessions/${session.id}`, { json: { status: "ending" } }),
@@ -36,7 +40,14 @@ function CurrentSessionCard({
transition={{ bounce: 0, delay: index * 0.1 }}
className="p-[1.389vw] rounded-[1.667vw] bg-white w-[18.889vw] flex flex-col gap-[0.833vw] shadow-[0px_4px_40px_0_rgba(0,0,0,0.05),0px_2px_2px_0_rgba(0,0,0,0.05)]"
>
<div className="flex justify-between gap-[0.833vw] items-center">
<div
className="flex justify-between gap-[0.833vw] items-center cursor-pointer"
onClick={() => {
if (session.status === "started") {
setModal(<CurrentSessionModal session={session} />);
}
}}
>
<span className="size-[1.111vw] text-[#7B60F3]">
<FlashIcon />
</span>
@@ -58,7 +69,11 @@ function CurrentSessionCard({
<ChevronRightIcon />
</span>
</div>
<NewButton variant="critical" onClick={() => endSession()}>
<NewButton
variant="critical"
onClick={() => endSession()}
disabled={isPending}
>
Завершить сеанс
</NewButton>
</motion.div>
+11 -14
View File
@@ -10,6 +10,7 @@ import UnlinkIcon from "./icons/UnlinkIcon";
import clsx from "clsx";
import ChevronRightIcon from "./icons/ChevronRightIcon";
import CurrentSessionModal from "./modals/CurrentSessionModal";
interface IDesktopCardProps {
server: IServer;
}
@@ -17,18 +18,6 @@ interface IDesktopCardProps {
export default function DesktopCard({ server }: IDesktopCardProps) {
const { setModal, setPosition } = useModalStore();
// const { mutate: createSession } = useMutation({
// mutationFn: () =>
// api.post(`sessions`, {
// json: {
// serverId: server.id,
// clientId: "abcfd570-2fa8-4f55-957b-5007f84f8f96",
// appId: "b8a9995c-a799-4593-8f96-03942050cb21",
// },
// }),
// onMutate: () => queryClient.invalidateQueries({ queryKey: ["sessions"] }),
// });
async function handleClickCreateSession() {
setPosition("right");
setModal(<CreateSessionModal targetServerId={server.id} />);
@@ -62,12 +51,20 @@ export default function DesktopCard({ server }: IDesktopCardProps) {
</div>
</div>
<div className="flex gap-[0.278vw] justify-center -mt-[0.278vw]">
{server.sessions?.[0]?.status === "started" ? (
{server.sessions?.[0]?.status === "starting" ? (
<div className="p-4 bg-white rounded-lg text-2xl outline">
загрузка...
</div>
) : server.sessions?.[0]?.status === "started" ? (
<div className="flex items-center gap-[0.278vw]">
<NewButton
variant="primary"
onClick={() => {
setModal(<CurrentSessionModal server={server} />);
if (server.sessions?.length && server.sessions?.[0]) {
setModal(
<CurrentSessionModal session={server.sessions[0]} />
);
}
}}
className="flex gap-[0.556vw] items-center"
>
+15 -8
View File
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import { IApp } from "../types/IApp";
import ChevronLeftIcon from "./icons/ChevronLeftIcon";
import CloseIcon from "./icons/CloseIcon";
@@ -11,18 +11,22 @@ interface Props {
projects: IApp[];
selectedProject: IApp | null;
setSelectedProject: (project: IApp | null) => void;
activeProject: IApp | null;
}
function ProjectSelector({
projects,
selectedProject,
setSelectedProject,
activeProject,
}: Props) {
const [isOpen, setIsOpen] = useState(false);
const [pointedProject, setPointedProject] = useState<IApp | null>(
selectedProject
);
const [pointedProject, setPointedProject] = useState<IApp | null>(null);
useEffect(() => {
setPointedProject(selectedProject);
}, [selectedProject]);
return (
<>
@@ -86,16 +90,19 @@ function ProjectSelector({
<div className="space-y-[0.278vw]">
<div className="flex items-center gap-[0.278vw]">
<p className="text-s">{project.name}</p>
<span className="size-[0.972vw] text-[#7B60F3]">
<LightningIcon />
</span>
{activeProject &&
project.name === activeProject.name && (
<span className="size-[0.972vw] text-[#7B60F3]">
<LightningIcon />
</span>
)}
</div>
<p className="caption-s text-[#7D7D7D] font-medium">
Доступно 128 квартир
</p>
</div>
</div>
{pointedProject?.id === project.id ? (
{pointedProject?.name === project.name ? (
<div className="size-[1.389vw] flex items-center justify-center rounded-full bg-[#7B60F3]">
<div className="size-[0.833vw] text-white">
<CheckIcon />
+70 -45
View File
@@ -1,5 +1,5 @@
import { IServer } from "../../types/IServer.ts";
import { useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { IApp } from "../../types/IApp.ts";
import api from "../../utils/api.ts";
import { ISession } from "../../types/ISession.ts";
@@ -22,11 +22,13 @@ export default function CreateSessionModal({ targetServerId }: Props) {
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
@@ -38,6 +40,12 @@ export default function CreateSessionModal({ targetServerId }: Props) {
);
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
@@ -53,20 +61,27 @@ export default function CreateSessionModal({ targetServerId }: Props) {
});
const { mutate: createSession } = useMutation({
mutationFn: (clientId: string) => {
return api
mutationKey: ["create-session", selectedServer?.id],
mutationFn: ({
clientId,
serverId,
appId,
}: {
clientId: string;
serverId: string;
appId: string;
}) =>
api
.post("sessions", {
json: {
clientId,
serverId: selectedServer?.id,
appId: selectedApp?.id,
serverId,
appId,
},
})
.json<ISession>();
},
.json<ISession>(),
onMutate: () => {
queryClient.invalidateQueries({ queryKey: ["sessions"] });
queryClient.invalidateQueries({ queryKey: ["last-started"] });
queryClient.invalidateQueries({ queryKey: ["servers"] });
setModal(null);
},
@@ -86,40 +101,43 @@ export default function CreateSessionModal({ targetServerId }: Props) {
if (!name || !phone || !selectedServer || !selectedApp) return;
if (
selectedServer?.sessions?.[0]?.status === "started" &&
!isSessionExists
) {
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;
}
if (isSessionExists) {
endSession(undefined, {
onSuccess: () => {
createClient(undefined, {
onSuccess: (client) => {
createSession(client.id);
},
onError: (error) => {
console.log(error);
},
});
},
onError: (error) => {
console.log("Ошибка при завершении сессии:", error);
},
});
} else {
createClient(undefined, {
onSuccess: (client) => {
createSession(client.id);
},
onError: (error) => {
console.log(error);
},
});
}
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);
@@ -165,13 +183,20 @@ export default function CreateSessionModal({ targetServerId }: Props) {
</div>
<div className="flex flex-col gap-y-[0.833vw]">
<p className="title-s font-medium">Выберите параметры сеанса</p>
{selectedServer?.apps && selectedServer?.apps?.length > 0 && (
<ProjectSelector
projects={selectedServer?.apps || []}
selectedProject={selectedApp ?? selectedServer?.apps?.[0] ?? null}
setSelectedProject={setSelectedApp}
/>
)}
{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]">
+14 -38
View File
@@ -1,35 +1,25 @@
import { intervalToDuration } from "date-fns";
import { IServer } from "../../types/IServer";
import FlashIcon from "../icons/FlashIcon";
import NewButton from "../NewButton";
import ChevronRightIcon from "../icons/ChevronRightIcon";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import api from "../../utils/api";
import useModalStore from "../../stores/useModalStore";
import { ISession } from "../../types/ISession";
import { useEffect, useState } from "react";
function CurrentSessionModal({ server }: { server: IServer }) {
function CurrentSessionModal({ session }: { session: ISession }) {
const queryClient = useQueryClient();
const { setModal } = useModalStore();
const { mutate: endSession } = useMutation({
mutationKey: ["sessions", server.sessions?.[0]?.id],
mutationKey: ["sessions", session.id],
mutationFn: () =>
api.put(`sessions/${server.sessions?.[0]?.id}`, {
api.put(`sessions/${session.id}`, {
json: { status: "ending" },
}),
onMutate: () => queryClient.invalidateQueries({ queryKey: ["sessions"] }),
});
const { data: currentSession } = useQuery({
queryKey: ["sessions", server.sessions?.[0]?.id],
queryFn: () =>
api.get(`sessions/${server.sessions?.[0]?.id}`).json<ISession>(),
});
console.log(currentSession);
const [now, setNow] = useState(Date.now());
useEffect(() => {
@@ -39,19 +29,7 @@ function CurrentSessionModal({ server }: { server: IServer }) {
return () => clearInterval(interval);
}, []);
useEffect(() => {
if (!currentSession) return;
const duration = intervalToDuration({
start: currentSession.createdAt,
end: now,
});
const hours = (duration.hours || 0).toString().padStart(2, "0");
const minutes = (duration.minutes || 0).toString().padStart(2, "0");
const seconds = (duration.seconds || 0).toString().padStart(2, "0");
console.log(`${hours}:${minutes}:${seconds}`);
}, [currentSession, now]);
if (!currentSession) return null;
if (!session) return null;
return (
<div className="w-[25vw] bg-[#FFFFFF] rounded-4xl px-[1.389vw] pb-[1.389vw]">
@@ -63,13 +41,15 @@ function CurrentSessionModal({ server }: { server: IServer }) {
<div className='size-[4.444vw] bg-[#F6F6F6] rounded-xl bg-[url("/images/super-table.png")] bg-no-repeat bg-[length:2.222vw] bg-center'></div>
<div className="flex flex-col gap-[0.278vw] self-center ">
<div className="flex gap-[0.278vw]">
<p className="title-s font-medium">{server.name}</p>
<p className="title-s font-medium">{session.server.name}</p>
<p className="flex justify-center items-center gap-[0.139vw] caption-s font-medium text-[#7B60F3]">
<FlashIcon />
<span className="size-[0.833vw] text-[#7B60F3]">
<FlashIcon />
</span>
Сеанс идёт{" "}
{(() => {
const duration = intervalToDuration({
start: currentSession.createdAt,
start: session.createdAt,
end: now,
});
const hours = (duration.hours || 0)
@@ -87,7 +67,7 @@ function CurrentSessionModal({ server }: { server: IServer }) {
</div>
<div>
<p className="caption-s font-medium text-[#BDBDBD]">
{server.location}
{session.server.location}
</p>
</div>
</div>
@@ -98,12 +78,10 @@ function CurrentSessionModal({ server }: { server: IServer }) {
<NewButton variant="secondary" className="w-full">
<div className="flex flex-col gap-[0.278vw] w-full text-left h-[2.222vw]">
<p className="caption-s font-medium text-[#BDBDBD]">Клиент</p>
<p className="text-s font-medium">
{currentSession.client.name}
</p>
<p className="text-s font-medium">{session.client.name}</p>
</div>
<div className="flex gap-[0.556vw] items-center">
{!currentSession.client.email && (
{!session.client.email && (
<p className="caption-s font-medium text-[#7B60F3] whitespace-nowrap">
Добавьте email
</p>
@@ -120,9 +98,7 @@ function CurrentSessionModal({ server }: { server: IServer }) {
<div>
<div className="flex gap-[0.556vw]">
<p className="caption-s font-medium text-[#BDBDBD]">Менеджер:</p>
<p className="caption-s font-medium">
{currentSession.owner.fullname}
</p>
<p className="caption-s font-medium">{session.owner.fullname}</p>
</div>
<div className="flex gap-[0.556vw]">
<p className="caption-s font-medium text-[#BDBDBD]">