upd
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -27,7 +27,7 @@ function Button({
|
||||
onClick?.(e);
|
||||
}}
|
||||
className={clsx(
|
||||
"transition-all flex outline-none 2xl:gap-[0.556vw] gap-2 items-center justify-center font-medium disabled:bg-[#F6F6F6] disabled:text-[#D6D6D6]",
|
||||
"transition-all flex outline-none 2xl:gap-[0.556vw] gap-2 items-center justify-center font-medium disabled:bg-[#F6F6F6] disabled:!text-[#D6D6D6]",
|
||||
variant === "critical" &&
|
||||
"text-[#FF4517] bg-[#FEF3F2] hover:bg-[#FEE4E2]",
|
||||
variant === "secondary" &&
|
||||
|
||||
@@ -1,11 +1,64 @@
|
||||
import { motion } from "motion/react";
|
||||
import { Comment } from "../types/Comment";
|
||||
import { format } from "date-fns";
|
||||
import { format, isToday } from "date-fns";
|
||||
import { Session } from "../types/Session";
|
||||
import { ru } from "date-fns/locale";
|
||||
import ChevronRightIcon from "./icons/ChevronRightIcon";
|
||||
import useModalStore from "../stores/useModalStore";
|
||||
import SessionModal from "./modals/SessionModal";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import api from "../utils/api";
|
||||
|
||||
function SessionCommentItem({
|
||||
comment,
|
||||
session,
|
||||
}: {
|
||||
comment: Comment;
|
||||
session?: Session;
|
||||
}) {
|
||||
const { setModal } = useModalStore();
|
||||
|
||||
const { data: files } = useQuery({
|
||||
queryKey: ["file-list", comment.sessionId],
|
||||
enabled: !!session,
|
||||
queryFn: () =>
|
||||
api
|
||||
.get("files", {
|
||||
searchParams: { sessionId: comment.sessionId },
|
||||
})
|
||||
.json<{ filename: string; size: number }[]>(),
|
||||
});
|
||||
|
||||
function SessionCommentItem({ comment }: { comment: Comment }) {
|
||||
return (
|
||||
<motion.div layout className="flex gap-[0.833vw] items-end">
|
||||
<div className="relative flex flex-col gap-[0.556vw] p-[0.833vw] bg-white rounded-[0.833vw] w-full rounded-br-none">
|
||||
{session && (
|
||||
<div
|
||||
className="relative bg-[#E1DEFC] rounded-[0.556vw] self-stretch p-[0.556vw] flex justify-between items-center cursor-pointer overflow-hidden"
|
||||
onClick={() => setModal(<SessionModal session={session} />)}
|
||||
>
|
||||
<div className="h-full w-[0.139vw] bg-[#7B60F3] left-0 absolute" />
|
||||
<div className="space-y-[0.278vw]">
|
||||
<p className="text-[#7B60F3] caption-m font-medium">
|
||||
Сеанс{" "}
|
||||
{isToday(new Date(session.createdAt))
|
||||
? "Сегодня"
|
||||
: `от ${format(new Date(session.createdAt), "dd MMMM", {
|
||||
locale: ru,
|
||||
})}`}
|
||||
</p>
|
||||
{files && (
|
||||
<p className="text-s">
|
||||
{files?.length}{" "}
|
||||
{files?.length === 1 ? "документ" : "документов"}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="size-[1.389vw] text-[#7B60F3]">
|
||||
<ChevronRightIcon />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<p className="button-m font-medium">{comment.manager.fullname}</p>
|
||||
<div className="flex flex-col max-w-[19.583vw]">
|
||||
<p className="caption-s break-words whitespace-pre-wrap overflow-hidden">
|
||||
@@ -18,7 +71,7 @@ function SessionCommentItem({ comment }: { comment: Comment }) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white size-[2.222vw] rounded-full flex-shrink-0" />
|
||||
<div className="bg-[url(/images/mock_manager_photo_c.png)] bg-cover bg-no-repeat bg-center size-[2.222vw] rounded-full flex-shrink-0" />
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ function SessionComments({ sessionId }: { sessionId: string }) {
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
<Button variant="cta" size="large" type="submit" disabled={!value}>
|
||||
<span className="size-[1.111vw] text-white">
|
||||
<span className="size-[1.111vw]">
|
||||
<SendIcon />
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
@@ -3,13 +3,13 @@ function SendIcon() {
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="m16 4-4.2 12-2.4-5.4L4 8.2z"
|
||||
stroke="#fff"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.2}
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="m15 5-5.5 5.5"
|
||||
stroke="#fff"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
|
||||
@@ -15,6 +15,7 @@ import SpinIcon from "../icons/SpinIcon";
|
||||
import useModalStore from "../../stores/useModalStore";
|
||||
import CreateSessionModal from "./CreateSessionModal";
|
||||
import SessionModal from "./SessionModal";
|
||||
import SessionCommentItem from "../SessionCommentItem";
|
||||
|
||||
function ClientModal({ client }: { client: Client }) {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -41,7 +42,7 @@ function ClientModal({ client }: { client: Client }) {
|
||||
<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 bg-[#F0F0F0] h-[calc(100vh-8.861vw)] rounded-b-[2.222vw] overflow-hidden">
|
||||
<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]">
|
||||
@@ -123,8 +124,12 @@ function ClientModal({ client }: { client: Client }) {
|
||||
</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" />
|
||||
{client.managers.map((manager) => (
|
||||
<div
|
||||
key={manager.id}
|
||||
className="size-[2.222vw] rounded-full bg-[#F0F0F0] bg-[url(/images/mock_manager_photo_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]">
|
||||
@@ -152,9 +157,7 @@ function ClientModal({ client }: { client: Client }) {
|
||||
<div
|
||||
key={session.id}
|
||||
className="p-[0.278vw] border-b border-[#F6F6F6] cursor-pointer"
|
||||
onClick={() => {
|
||||
setModal(<SessionModal session={session} />);
|
||||
}}
|
||||
onClick={() => setModal(<SessionModal session={session} />)}
|
||||
>
|
||||
<div className="p-[0.833vw] flex justify-between items-center">
|
||||
<div className="flex gap-[0.556vw] items-center">
|
||||
@@ -166,9 +169,13 @@ function ClientModal({ client }: { client: Client }) {
|
||||
<p className="caption-s text-[#BDBDBD] font-medium">
|
||||
{isToday(new Date(session.updatedAt))
|
||||
? "Сегодня"
|
||||
: format(new Date(session.updatedAt), "d MMMM", {
|
||||
locale: ru,
|
||||
})}
|
||||
: format(
|
||||
new Date(session.updatedAt),
|
||||
"dd.MM.yyyy",
|
||||
{
|
||||
locale: ru,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -196,7 +203,48 @@ function ClientModal({ client }: { client: Client }) {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1"></div>
|
||||
<div className="flex-1 overflow-auto px-[1.111vw] py-[1.667vw] [scrollbar-width:thin] flex flex-col-reverse">
|
||||
{client.sessions
|
||||
.filter((session) => session.comments.length)
|
||||
.map((session) => (
|
||||
<div key={session.id} className="space-y-[1.111vw]">
|
||||
<p className="text-center text-[#BDBDBD] caption-s font-medium">
|
||||
{isToday(new Date(session.createdAt))
|
||||
? "Сегодня"
|
||||
: format(new Date(session.createdAt), "dd MMMM", {
|
||||
locale: ru,
|
||||
})}
|
||||
</p>
|
||||
<div className="space-y-[0.833vw]">
|
||||
{session.comments.map((comment) => (
|
||||
<SessionCommentItem
|
||||
key={comment.id}
|
||||
comment={comment}
|
||||
session={session}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{(client.sessions.length === 0 ||
|
||||
client.sessions.filter((session) => session.comments.length)
|
||||
.length === 0) && (
|
||||
<div className="flex flex-col items-center gap-[1.111vw] w-[18.333vw] m-auto">
|
||||
<div className="w-[13.889vw]">
|
||||
<img src="/images/empty_ghost.png" alt="" />
|
||||
</div>
|
||||
<div className="space-y-[0.556vw]">
|
||||
<p className="text-center title-m font-medium">
|
||||
Пока что пусто
|
||||
</p>
|
||||
<p className="caption-s text-[#BDBDBD] font-medium text-center">
|
||||
Здесь отображаются все комментарии по сеансам с текущим
|
||||
клиентом
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function CreateSessionModal({ targetServerId, client }: Props) {
|
||||
useEffect(() => {
|
||||
setSelectedApp(
|
||||
selectedServer?.sessions?.[0]?.app ||
|
||||
selectedServer?.apps?.[0].app ||
|
||||
selectedServer?.appsToServers?.[0].app ||
|
||||
null
|
||||
);
|
||||
}, [selectedServer]);
|
||||
@@ -244,15 +244,15 @@ export default function CreateSessionModal({ targetServerId, client }: Props) {
|
||||
<div className="flex flex-col gap-y-[0.833vw]">
|
||||
<p className="title-s font-medium">Выберите параметры сеанса</p>
|
||||
{selectedServer &&
|
||||
selectedServer?.apps &&
|
||||
selectedServer?.apps?.length > 0 && (
|
||||
selectedServer?.appsToServers &&
|
||||
selectedServer?.appsToServers?.length > 0 && (
|
||||
<ProjectSelector
|
||||
activeProject={
|
||||
selectedServer?.sessions?.[0]?.status === "started"
|
||||
? selectedApp
|
||||
: null
|
||||
}
|
||||
projects={selectedServer?.apps.map(({ app }) => app)}
|
||||
projects={selectedServer?.appsToServers.map(({ app }) => app)}
|
||||
selectedProject={selectedApp}
|
||||
setSelectedProject={setSelectedApp}
|
||||
/>
|
||||
|
||||
@@ -1,28 +1,42 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import useModalStore from "../../stores/useModalStore";
|
||||
import { Session } from "../../types/Session";
|
||||
import Button from "../Button";
|
||||
import CurrentSessionModal from "./CurrentSessionModal";
|
||||
import api from "../../utils/api";
|
||||
import SpinIcon from "../icons/SpinIcon";
|
||||
import SessionModal from "./SessionModal";
|
||||
import { Server } from "../../types/Server";
|
||||
|
||||
function EndSessionModal({ session }: { session: Session }) {
|
||||
const queryClient = useQueryClient();
|
||||
const { setModal } = useModalStore();
|
||||
|
||||
const { setModal, setPosition } = useModalStore();
|
||||
|
||||
const { mutate: endSession, isPending } = useMutation({
|
||||
mutationKey: ["sessions", session.id],
|
||||
mutationFn: () =>
|
||||
api.put(`sessions/${session.id}`, { json: { status: "ending" } }),
|
||||
api
|
||||
.put(`sessions/${session.id}`, { json: { status: "ending" } })
|
||||
.json<Session>(),
|
||||
onMutate: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["sessions"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["last-started"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["servers"] });
|
||||
// queryClient.invalidateQueries({ queryKey: ["sessions"] });
|
||||
// queryClient.invalidateQueries({ queryKey: ["last-started"] });
|
||||
// queryClient.invalidateQueries({ queryKey: ["servers"] });
|
||||
setModal(null);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["servers"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["last-sessions"] });
|
||||
setModal(null);
|
||||
queryClient.invalidateQueries({ queryKey: ["sessions"] });
|
||||
const servers = queryClient.getQueryData<Server[]>(["servers"]);
|
||||
const updatedSession = servers
|
||||
?.find((s) => s.id === session.serverId)
|
||||
?.sessions?.find((s) => s.id === session.id);
|
||||
if (updatedSession) {
|
||||
setPosition("right");
|
||||
setModal(<SessionModal session={updatedSession} />);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ import DownloadIcon from "../icons/DownloadIcon";
|
||||
import ShareIcon from "../icons/ShareIcon";
|
||||
|
||||
function SessionModal({ session }: { session: Session }) {
|
||||
const { data } = useQuery({
|
||||
queryKey: ["file-list"],
|
||||
const { data: files } = useQuery({
|
||||
queryKey: ["file-list", session.id],
|
||||
queryFn: () =>
|
||||
api
|
||||
.get("files", {
|
||||
@@ -122,13 +122,13 @@ function SessionModal({ session }: { session: Session }) {
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
{data && (
|
||||
{files && (
|
||||
<div className="flex flex-col gap-[1.111vw] bg-white rounded-[1.667vw] p-[1.111vw]">
|
||||
<h3 className="title-s flex items-center font-medium gap-[0.556vw]">
|
||||
<span>Документы по сеансу</span>
|
||||
<Badge count={data?.length} />
|
||||
<Badge count={files?.length} />
|
||||
</h3>
|
||||
<SessionFiles files={data} session={session} />
|
||||
<SessionFiles files={files} session={session} />
|
||||
<div className="flex w-full gap-[0.556vw]">
|
||||
<Button variant="primary" size="large" className="w-full">
|
||||
<span className="size-[1.111vw] text-[#7B60F3]">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Manager } from "./Manager";
|
||||
import { Session } from "./Session";
|
||||
|
||||
export interface Comment {
|
||||
id: string;
|
||||
@@ -8,4 +9,5 @@ export interface Comment {
|
||||
managerId: string;
|
||||
sessionId: string;
|
||||
manager: Manager;
|
||||
session: Session;
|
||||
}
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ export interface Server {
|
||||
description: string;
|
||||
companyId: string;
|
||||
sessions?: Session[];
|
||||
apps?: { app: App }[];
|
||||
appsToServers?: { app: App }[];
|
||||
status: "online" | "offline";
|
||||
ipAddress: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user