upd
This commit is contained in:
@@ -1,5 +1 @@
|
||||
<<<<<<< HEAD
|
||||
VITE_API_URL=http://192.168.1.204:3000/
|
||||
=======
|
||||
VITE_API_URL=http://192.168.1.204:3000
|
||||
>>>>>>> b8d191113582cbbfeaea9d0bea544c445a25c2ac
|
||||
VITE_API_URL=http://192.168.1.170:3000/
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"@tanstack/react-query": "^5.67.3",
|
||||
"@uidotdev/usehooks": "^2.4.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"ky": "^1.7.5",
|
||||
"motion": "^12.5.0",
|
||||
"react": "^19.0.0",
|
||||
@@ -381,6 +382,8 @@
|
||||
|
||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||
|
||||
"date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="],
|
||||
|
||||
"debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
|
||||
|
||||
"dedent": ["dedent@1.5.3", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ=="],
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"@tanstack/react-query": "^5.67.3",
|
||||
"@uidotdev/usehooks": "^2.4.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"ky": "^1.7.5",
|
||||
"motion": "^12.5.0",
|
||||
"react": "^19.0.0",
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import FlashIcon from "./icons/FlashIcon";
|
||||
import { ISession } from "../types/ISession";
|
||||
import NewButton from "./NewButton";
|
||||
import api from "../utils/api";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import ChevronRightIcon from "./icons/ChevronRightIcon";
|
||||
import { motion } from "motion/react";
|
||||
|
||||
function CurrentSessionCard({
|
||||
session,
|
||||
index,
|
||||
}: {
|
||||
session: ISession;
|
||||
index: number;
|
||||
}) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutate: endSession } = useMutation({
|
||||
mutationKey: ["sessions", session.id],
|
||||
mutationFn: () =>
|
||||
api.put(`sessions/${session.id}`, { json: { status: "ending" } }),
|
||||
onMutate: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["sessions"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["last-started"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["servers"] });
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key={session.id}
|
||||
layout
|
||||
initial={{ x: "100%" }}
|
||||
animate={{ x: "0%" }}
|
||||
exit={{ x: "100%" }}
|
||||
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">
|
||||
<span className="size-[1.111vw] text-[#7B60F3]">
|
||||
<FlashIcon />
|
||||
</span>
|
||||
<div className="flex flex-col items-center gap-y-[0.278vw]">
|
||||
<p className="button-m font-medium text-[#7B60F3] text-center">
|
||||
Текущий сеанс
|
||||
</p>
|
||||
<div className="flex items-center gap-[0.278vw]">
|
||||
<p className="caption-s font-medium text-[#7D7D7D]">
|
||||
{session.server.name}
|
||||
</p>
|
||||
<div className="size-[0.139vw] bg-[#7D7D7D] rounded-full" />
|
||||
<p className="caption-s font-medium text-[#7D7D7D]">
|
||||
{session.owner.fullname}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="size-[1.111vw] text-[#7D7D7D]">
|
||||
<ChevronRightIcon />
|
||||
</span>
|
||||
</div>
|
||||
<NewButton variant="critical" onClick={() => endSession()}>
|
||||
Завершить сеанс
|
||||
</NewButton>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CurrentSessionCard;
|
||||
@@ -1,15 +1,41 @@
|
||||
import { Outlet } from "react-router";
|
||||
import Navbar from "./Navbar";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import api from "../utils/api";
|
||||
import CurrentSessionCard from "./CurrentSessionCard";
|
||||
import { ISession } from "../types/ISession";
|
||||
import { AnimatePresence } from "motion/react";
|
||||
|
||||
function Layout() {
|
||||
const { data: currentStartedSessions } = useQuery({
|
||||
queryKey: ["sessions", "last-started"],
|
||||
queryFn: () =>
|
||||
api
|
||||
.get("sessions", {
|
||||
searchParams: { limit: 3, status: "started" },
|
||||
})
|
||||
.json<ISession[]>(),
|
||||
refetchInterval: 1000,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex">
|
||||
<div className="flex gap-[1.667vw] overflow-hidden">
|
||||
<div className="flex-1"></div>
|
||||
<div className="w-[42.5vw] flex flex-col gap-[1.667vw]">
|
||||
<Navbar />
|
||||
<Outlet />
|
||||
</div>
|
||||
<div className="flex-1"></div>
|
||||
<div className="flex-1 flex flex-col items-center gap-y-[0.833vw] py-[1.667vw]">
|
||||
<AnimatePresence>
|
||||
{currentStartedSessions?.map((session, index, { length }) => (
|
||||
<CurrentSessionCard
|
||||
key={session.id}
|
||||
session={session}
|
||||
index={length - index}
|
||||
/>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { ISession } from "../types/ISession";
|
||||
|
||||
function SessionCard({ session }: { session: ISession }) {
|
||||
console.log(session);
|
||||
return (
|
||||
<div className='w-full h-[4.444vw] border-1 border-l-0 border-r-0 border-t-0 border-b-[#F6F6F6] flex py-[0.278vw] items-center gap-[0.556vw] cursor-pointer group'>
|
||||
<div className='rounded-xl w-full h-full flex items-center gap-[0.556vw] group-hover:bg-[#F6F6F6] transition-colors duration-200'>
|
||||
<div className='size-[2.5vw] bg-[#F6F6F6] rounded-full'></div>
|
||||
<div className='flex flex-col w-full gap-[0.278vw]'>
|
||||
<p className='button-m font-medium'>{session.owner.fullname}</p>
|
||||
<p className='caption-s font-medium text-[#7D7D7D]'>
|
||||
<div className="w-full h-[4.444vw] border-1 border-l-0 border-r-0 border-t-0 border-b-[#F6F6F6] flex py-[0.278vw] items-center gap-[0.556vw] cursor-pointer group">
|
||||
<div className="rounded-xl w-full h-full flex items-center gap-[0.556vw] group-hover:bg-[#F6F6F6] transition-colors duration-200">
|
||||
<div className="size-[2.5vw] bg-[#F6F6F6] rounded-full"></div>
|
||||
<div className="flex flex-col w-full gap-[0.278vw]">
|
||||
<p className="button-m font-medium">{session.owner.fullname}</p>
|
||||
<p className="caption-s font-medium text-[#7D7D7D]">
|
||||
Клиент: {session.client.name} •
|
||||
{session.app.name}
|
||||
</p>
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
function FlashIcon() {
|
||||
return (
|
||||
<svg
|
||||
width={20}
|
||||
height={20}
|
||||
viewBox='0 0 20 20'
|
||||
fill='currentColor'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d='M8.71094 3H13.62L10.62 7.9H14.7109L8.16548 17L9.52912 10.35H5.71094L8.71094 3Z'
|
||||
fill='currentColor'
|
||||
d="M8.71094 3H13.62L10.62 7.9H14.7109L8.16548 17L9.52912 10.35H5.71094L8.71094 3Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
@@ -28,6 +28,7 @@ export default function CreateSessionModal({
|
||||
targetServer
|
||||
);
|
||||
const [selectedApp, setSelectedApp] = useState<IApp | null>(null);
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutate: createClient } = useMutation({
|
||||
@@ -56,8 +57,9 @@ export default function CreateSessionModal({
|
||||
})
|
||||
.json<ISession>();
|
||||
},
|
||||
onSuccess: () => {
|
||||
onMutate: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["sessions"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["last-started"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["servers"] });
|
||||
setModal(null);
|
||||
},
|
||||
@@ -121,7 +123,7 @@ export default function CreateSessionModal({
|
||||
</div>
|
||||
<div className="flex flex-col gap-y-[0.833vw]">
|
||||
<p className="title-s font-medium">Выберите параметры сеанса</p>
|
||||
{selectedServer?.apps?.length && selectedServer?.apps?.length > 0 && (
|
||||
{selectedServer?.apps && selectedServer?.apps?.length > 0 && (
|
||||
<ProjectSelector
|
||||
projects={selectedServer?.apps || []}
|
||||
selectedProject={selectedApp ?? selectedServer?.apps?.[0] ?? null}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { intervalToDuration } from "date-fns";
|
||||
import { IServer } from "../../types/IServer";
|
||||
import FlashIcon from "../icons/FlashIcon";
|
||||
import NewButton from "../NewButton";
|
||||
@@ -6,6 +7,7 @@ import { useMutation, useQuery, 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 }) {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -20,13 +22,36 @@ function CurrentSessionModal({ server }: { server: IServer }) {
|
||||
onMutate: () => queryClient.invalidateQueries({ queryKey: ["sessions"] }),
|
||||
});
|
||||
|
||||
const { data: currentServer } = useQuery({
|
||||
const { data: currentSession } = useQuery({
|
||||
queryKey: ["sessions", server.sessions?.[0]?.id],
|
||||
queryFn: () =>
|
||||
api.get(`sessions/${server.sessions?.[0]?.id}`).json<ISession>(),
|
||||
});
|
||||
|
||||
console.log(currentServer);
|
||||
console.log(currentSession);
|
||||
|
||||
const [now, setNow] = useState(Date.now());
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setNow(Date.now());
|
||||
}, 1000);
|
||||
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;
|
||||
|
||||
return (
|
||||
<div className="w-[25vw] bg-[#FFFFFF] rounded-4xl px-[1.389vw] pb-[1.389vw]">
|
||||
@@ -41,7 +66,23 @@ function CurrentSessionModal({ server }: { server: IServer }) {
|
||||
<p className="title-s font-medium">{server.name}</p>
|
||||
<p className="flex justify-center items-center gap-[0.139vw] caption-s font-medium text-[#7B60F3]">
|
||||
<FlashIcon />
|
||||
Сеанс идёт 24:05
|
||||
Сеанс идёт{" "}
|
||||
{(() => {
|
||||
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");
|
||||
return `${hours}:${minutes}:${seconds}`;
|
||||
})()}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
@@ -57,10 +98,12 @@ 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">{server.client?.name}</p>
|
||||
<p className="text-s font-medium">
|
||||
{currentSession.client.name}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-[0.556vw] items-center">
|
||||
{!server.client?.email && (
|
||||
{!currentSession.client.email && (
|
||||
<p className="caption-s font-medium text-[#7B60F3] whitespace-nowrap">
|
||||
Добавьте email
|
||||
</p>
|
||||
@@ -77,7 +120,9 @@ 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">{server.owner?.fullname}</p>
|
||||
<p className="caption-s font-medium">
|
||||
{currentSession.owner.fullname}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-[0.556vw]">
|
||||
<p className="caption-s font-medium text-[#BDBDBD]">
|
||||
|
||||
+15
-15
@@ -43,25 +43,25 @@ function DashboardPage() {
|
||||
// }
|
||||
|
||||
return (
|
||||
<div className='flex flex-col gap-[5vw] min-h-dvh'>
|
||||
<div className='w-full flex justify-between'>
|
||||
<div className='w-[42.5vw] flex flex-col gap-[1.667vw]'>
|
||||
<div className='flex items-center gap-[0.833vw]'>
|
||||
<h1 className='title-l font-[500] '>Интерактивные столы</h1>
|
||||
<div className="flex flex-col gap-[5vw] min-h-dvh">
|
||||
<div className="w-full flex justify-between">
|
||||
<div className="w-[42.5vw] flex flex-col gap-[1.667vw]">
|
||||
<div className="flex items-center gap-[0.833vw]">
|
||||
<h1 className="title-l font-[500] ">Интерактивные столы</h1>
|
||||
<Badge count={servers?.length || 0} />
|
||||
</div>
|
||||
<div className='flex gap-[0.833vw] flex-wrap'>
|
||||
<div className="flex gap-[0.833vw] flex-wrap">
|
||||
{servers?.map((server) => (
|
||||
<DesktopCard key={server.id} server={server} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='w-full'>
|
||||
<div className='flex flex-col gap-[1.667vw]'>
|
||||
<h1 className='title-l font-[500] '>Последние сеансы</h1>
|
||||
<div className='w-full flex flex-col gap-[0.833vw]'>
|
||||
<div className='flex flex-col gap-[0.278vw]'>
|
||||
<div className="w-full">
|
||||
<div className="flex flex-col gap-[1.667vw]">
|
||||
<h1 className="title-l font-[500] ">Последние сеансы</h1>
|
||||
<div className="w-full flex flex-col gap-[0.833vw]">
|
||||
<div className="flex flex-col gap-[0.278vw]">
|
||||
{sessions
|
||||
?.filter((session) => session.status === "ended")
|
||||
.map((session) => (
|
||||
@@ -69,12 +69,12 @@ function DashboardPage() {
|
||||
))}
|
||||
</div>
|
||||
<NewButton
|
||||
variant='primary'
|
||||
size='large'
|
||||
className='flex gap-[0.556vw] items-center justify-center'
|
||||
variant="primary"
|
||||
size="large"
|
||||
className="flex gap-[0.556vw] items-center justify-center"
|
||||
>
|
||||
<p>Смотреть всё</p>
|
||||
<span className='w-[1.111vw] h-[1.111vw] flex items-center justify-center'>
|
||||
<span className="w-[1.111vw] h-[1.111vw] flex items-center justify-center">
|
||||
<ChevronRightIcon />
|
||||
</span>
|
||||
</NewButton>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { IApp } from "./IApp";
|
||||
import { IClient } from "./IClient";
|
||||
import { IOwner } from "./IOwner";
|
||||
import { ISession } from "./ISession";
|
||||
|
||||
export interface IServer {
|
||||
@@ -12,6 +10,4 @@ export interface IServer {
|
||||
sessions?: ISession[];
|
||||
apps?: IApp[];
|
||||
status: "online" | "offline";
|
||||
client?: IClient;
|
||||
owner?: IOwner;
|
||||
}
|
||||
|
||||
@@ -14,4 +14,5 @@ export interface ISession {
|
||||
client: IUser;
|
||||
app: IApp;
|
||||
owner: IOwner;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user