From a4a3fde94015d771347d97bcaef19bb5155dee0b Mon Sep 17 00:00:00 2001 From: c00b3r Date: Fri, 6 Jun 2025 18:35:12 +0500 Subject: [PATCH] feat: add ClientCard and SessionModal components, enhance session handling with comments and duration utilities --- public/images/smile-ghost.png | Bin 0 -> 13931 bytes src/components/ClientCard.tsx | 28 ++++ src/components/CurrentSessionCard.tsx | 3 + src/components/DesktopCard.tsx | 2 +- src/components/SessionCard.tsx | 11 +- src/components/icons/DownloadIcon.tsx | 15 ++ src/components/icons/MagicIcon.tsx | 12 ++ src/components/icons/ShareIcon.tsx | 15 ++ src/components/modals/CreateSessionModal.tsx | 2 +- src/components/modals/CurrentSessionModal.tsx | 10 +- src/components/modals/SessionModal.tsx | 150 ++++++++++++++++++ src/pages/DashboardPage.tsx | 12 +- src/types/IComments.ts | 8 + src/types/ISession.ts | 3 + src/utils/interval-duration.tsx | 14 ++ 15 files changed, 279 insertions(+), 6 deletions(-) create mode 100644 public/images/smile-ghost.png create mode 100644 src/components/ClientCard.tsx create mode 100644 src/components/icons/DownloadIcon.tsx create mode 100644 src/components/icons/MagicIcon.tsx create mode 100644 src/components/icons/ShareIcon.tsx create mode 100644 src/components/modals/SessionModal.tsx create mode 100644 src/types/IComments.ts create mode 100644 src/utils/interval-duration.tsx diff --git a/public/images/smile-ghost.png b/public/images/smile-ghost.png new file mode 100644 index 0000000000000000000000000000000000000000..ce23bb852284e4b7b560601aa2555c4ae2aabb4c GIT binary patch literal 13931 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 C{>x(k literal 0 HcmV?d00001 diff --git a/src/components/ClientCard.tsx b/src/components/ClientCard.tsx new file mode 100644 index 0000000..59046fc --- /dev/null +++ b/src/components/ClientCard.tsx @@ -0,0 +1,28 @@ +import { IUser } from "../types/IUser"; +import ChevronRightIcon from "./icons/ChevronRightIcon"; +import NewButton from "./NewButton"; + +function ClientCard({ client }: { client: IUser }) { + return ( + <> + +
+

Клиент

+

{client.name}

+
+
+ {!client.email && ( +

+ Добавьте email +

+ )} + + + +
+
+ + ); +} + +export default ClientCard; diff --git a/src/components/CurrentSessionCard.tsx b/src/components/CurrentSessionCard.tsx index 91401e1..b0c8929 100644 --- a/src/components/CurrentSessionCard.tsx +++ b/src/components/CurrentSessionCard.tsx @@ -28,6 +28,9 @@ function CurrentSessionCard({ queryClient.invalidateQueries({ queryKey: ["last-started"] }); queryClient.invalidateQueries({ queryKey: ["servers"] }); }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["last-sessions"] }); + }, }); return ( diff --git a/src/components/DesktopCard.tsx b/src/components/DesktopCard.tsx index 8ff993b..028d5e1 100644 --- a/src/components/DesktopCard.tsx +++ b/src/components/DesktopCard.tsx @@ -84,7 +84,7 @@ export default function DesktopCard({ server }: IDesktopCardProps) { ) : server.status === "offline" ? ( - + diff --git a/src/components/SessionCard.tsx b/src/components/SessionCard.tsx index db1f79f..26d3e3c 100644 --- a/src/components/SessionCard.tsx +++ b/src/components/SessionCard.tsx @@ -1,8 +1,17 @@ +import useModalStore from "../stores/useModalStore"; import { ISession } from "../types/ISession"; +import SessionModal from "./modals/SessionModal"; function SessionCard({ session }: { session: ISession }) { + const { setModal, setPosition } = useModalStore(); return ( -
+
{ + setModal(); + setPosition("right"); + }} + >
diff --git a/src/components/icons/DownloadIcon.tsx b/src/components/icons/DownloadIcon.tsx new file mode 100644 index 0000000..fff22ac --- /dev/null +++ b/src/components/icons/DownloadIcon.tsx @@ -0,0 +1,15 @@ +function DownloadIcon() { + return ( + + + + ); +} + +export default DownloadIcon; diff --git a/src/components/icons/MagicIcon.tsx b/src/components/icons/MagicIcon.tsx new file mode 100644 index 0000000..654f352 --- /dev/null +++ b/src/components/icons/MagicIcon.tsx @@ -0,0 +1,12 @@ +function MagicIcon() { + return ( + + + + ); +} + +export default MagicIcon; diff --git a/src/components/icons/ShareIcon.tsx b/src/components/icons/ShareIcon.tsx new file mode 100644 index 0000000..4ee68a6 --- /dev/null +++ b/src/components/icons/ShareIcon.tsx @@ -0,0 +1,15 @@ +function ShareIcon() { + return ( + + + + ); +} + +export default ShareIcon; diff --git a/src/components/modals/CreateSessionModal.tsx b/src/components/modals/CreateSessionModal.tsx index 6f0a9b8..b644e50 100644 --- a/src/components/modals/CreateSessionModal.tsx +++ b/src/components/modals/CreateSessionModal.tsx @@ -144,7 +144,7 @@ export default function CreateSessionModal({ targetServerId }: Props) { return (
diff --git a/src/components/modals/CurrentSessionModal.tsx b/src/components/modals/CurrentSessionModal.tsx index 1d83d0a..6d36050 100644 --- a/src/components/modals/CurrentSessionModal.tsx +++ b/src/components/modals/CurrentSessionModal.tsx @@ -18,7 +18,15 @@ function CurrentSessionModal({ session }: { session: ISession }) { api.put(`sessions/${session.id}`, { json: { status: "ending" }, }), - onMutate: () => queryClient.invalidateQueries({ queryKey: ["sessions"] }), + onMutate: () => + queryClient.invalidateQueries({ + queryKey: ["sessions"], + }), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["last-sessions"], + }); + }, }); const [now, setNow] = useState(Date.now()); diff --git a/src/components/modals/SessionModal.tsx b/src/components/modals/SessionModal.tsx new file mode 100644 index 0000000..2fd9999 --- /dev/null +++ b/src/components/modals/SessionModal.tsx @@ -0,0 +1,150 @@ +import { ISession } from "../../types/ISession"; +import { format } from "date-fns"; +import { ru } from "date-fns/locale"; +import getIntervalDuration from "../../utils/interval-duration"; +import MagicIcon from "../icons/MagicIcon"; +import NewButton from "../NewButton"; +import ChevronRightIcon from "../icons/ChevronRightIcon"; +import Badge from "../Badge"; +import ClientCard from "../ClientCard"; +import DownloadIcon from "../icons/DownloadIcon"; +import ShareIcon from "../icons/ShareIcon"; + +function SessionModal({ session }: { session: ISession }) { + console.log(session); + return ( +
+
+
+

{format(session.createdAt, "dd MMMM yyyy", { locale: ru })}

+

, {format(session.createdAt, "HH:mm")}

+
+
+
+
+
+
+
+

{session.owner.fullname}

+

+ Продолжительность:{" "} + {getIntervalDuration(session.createdAt, session.updatedAt)} +

+
+
+
+

Информация о сеансе

+
+

+ + Сценарии: + + Добавить в бэкенд +

+

+ + Интерактивный стол: + + + {session.server.name} + +

+

+ + Продолжительность сеанса: + + + {getIntervalDuration(session.createdAt, session.updatedAt)} + +

+

+ + Проект: + + + {session.app.name} + +

+
+ +
+
+

+ Речевая  + + аналитика  + + + + +

+
+

+ + Эффективность встречи: + + + Высокая + +

+

+ + Бюджет клиента: + + 8 500 000 ₽ +

+
+
+ Клиент проявил высокий интерес к объекту, особенно к варианту с + улучшенной отделкой. Основной вопрос для принятия решения — + согласование с семьей и выбор этажа. Необходимо подготовить + предварительный договор к следующей встрече. +
+ + Весь отчёт по встречи + +
+
+

+ Документы по сеансу +

+
+ + + + + Скачать архивом + + + + + + +
+
+
+
+
+ {session.comments.length > 0 ? ( +
Комменты
+ ) : ( +
+ ghost +
+

Оставьте заметку

+

+ {`В дальнейшем это поможет быстро найти + клиента и не запутаться.`} +

+
+
+ )} +
+
+
+
+
+ ); +} + +export default SessionModal; diff --git a/src/pages/DashboardPage.tsx b/src/pages/DashboardPage.tsx index 70f2d5e..fa1316c 100644 --- a/src/pages/DashboardPage.tsx +++ b/src/pages/DashboardPage.tsx @@ -8,6 +8,7 @@ import { ISession } from "../types/ISession"; import SessionCard from "../components/SessionCard"; import NewButton from "../components/NewButton"; import ChevronRightIcon from "../components/icons/ChevronRightIcon"; +import { useEffect } from "react"; function DashboardPage() { const { data: me } = useQuery({ @@ -23,7 +24,7 @@ function DashboardPage() { }); const { data: sessions } = useQuery({ - queryKey: ["sessions", { limit: 5 }], + queryKey: ["last-sessions"], queryFn: () => api.get("sessions", { searchParams: { limit: 5 } }).json(), enabled: !!me, @@ -42,6 +43,10 @@ function DashboardPage() { // } // } + useEffect(() => { + console.log(sessions); + }, [sessions]); + return (
@@ -63,7 +68,10 @@ function DashboardPage() {
{sessions - ?.filter((session) => session.status === "ended") + ?.filter( + (session) => + session.status === "ended" || session.status === "ending" + ) .map((session) => ( ))} diff --git a/src/types/IComments.ts b/src/types/IComments.ts new file mode 100644 index 0000000..f1f625a --- /dev/null +++ b/src/types/IComments.ts @@ -0,0 +1,8 @@ +export interface IComment { + id: string; + text: string; + createdAt: Date; + updatedAt: Date; + ownerId: string; + sessionId: string; +} diff --git a/src/types/ISession.ts b/src/types/ISession.ts index 7461e3b..b974465 100644 --- a/src/types/ISession.ts +++ b/src/types/ISession.ts @@ -1,4 +1,5 @@ import { IApp } from "./IApp"; +import { IComment } from "./IComments"; import { IOwner } from "./IOwner"; import { IServer } from "./IServer"; import { IUser } from "./IUser"; @@ -9,10 +10,12 @@ export interface ISession { serverId: string; clientId: string; companyId: string; + comments: IComment[]; status: "starting" | "started" | "restarted" | "ending" | "ended"; server: IServer; client: IUser; app: IApp; owner: IOwner; createdAt: Date; + updatedAt: Date; } diff --git a/src/utils/interval-duration.tsx b/src/utils/interval-duration.tsx new file mode 100644 index 0000000..49861df --- /dev/null +++ b/src/utils/interval-duration.tsx @@ -0,0 +1,14 @@ +import { intervalToDuration } from "date-fns"; + +function getIntervalDuration(start: Date, end: Date) { + const duration = intervalToDuration({ + start: start, + end: end, + }); + 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}`; +} + +export default getIntervalDuration;