diff --git a/src/components/ClientCard.tsx b/src/components/ClientCard.tsx index 59046fc..cceda6d 100644 --- a/src/components/ClientCard.tsx +++ b/src/components/ClientCard.tsx @@ -8,7 +8,7 @@ function ClientCard({ client }: { client: IUser }) {

Клиент

-

{client.name}

+

{client.fullname}

{!client.email && ( diff --git a/src/components/CurrentSessionCard.tsx b/src/components/CurrentSessionCard.tsx index b0c8929..6059f56 100644 --- a/src/components/CurrentSessionCard.tsx +++ b/src/components/CurrentSessionCard.tsx @@ -1,5 +1,5 @@ import FlashIcon from "./icons/FlashIcon"; -import { ISession } from "../types/ISession"; +import { Session } from "../types/ISession"; import NewButton from "./NewButton"; import api from "../utils/api"; import { useMutation, useQueryClient } from "@tanstack/react-query"; @@ -12,7 +12,7 @@ function CurrentSessionCard({ session, index, }: { - session: ISession; + session: Session; index: number; }) { const { setModal } = useModalStore(); diff --git a/src/components/DesktopCard.tsx b/src/components/DesktopCard.tsx index 028d5e1..65f5126 100644 --- a/src/components/DesktopCard.tsx +++ b/src/components/DesktopCard.tsx @@ -1,4 +1,4 @@ -import { IServer } from "../types/IServer"; +import { Server } from "../types/IServer"; import useModalStore from "../stores/useModalStore"; import CreateSessionModal from "./modals/CreateSessionModal"; import NewButton from "./NewButton"; @@ -13,7 +13,7 @@ import CurrentSessionModal from "./modals/CurrentSessionModal"; import SpinIcon from "./icons/SpinIcon"; interface IDesktopCardProps { - server: IServer; + server: Server; } export default function DesktopCard({ server }: IDesktopCardProps) { diff --git a/src/components/DesktopSelect.tsx b/src/components/DesktopSelect.tsx index c3e46a0..a9f4b7d 100644 --- a/src/components/DesktopSelect.tsx +++ b/src/components/DesktopSelect.tsx @@ -1,13 +1,13 @@ import { useState } from "react"; -import { IServer } from "../types/IServer"; +import { Server } from "../types/IServer"; import Button from "./Button"; import { useClickAway } from "@uidotdev/usehooks"; import ArrowDownIcon from "./icons/ArrowDownIcon"; interface Props { - servers: IServer[] | undefined; - value: IServer | undefined; - onChange: (server: IServer) => void; + servers: Server[] | undefined; + value: Server | undefined; + onChange: (server: Server) => void; } export default function DesktopSelect({ servers, value, onChange }: Props) { diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 4071078..2de1d51 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -3,7 +3,7 @@ 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 { Session } from "../types/ISession"; import { AnimatePresence } from "motion/react"; import NewButton from "./NewButton"; import PlusIcon from "./icons/PlusIcon"; @@ -18,7 +18,7 @@ function Layout() { .get("sessions", { searchParams: { limit: 3, status: "started" }, }) - .json(), + .json(), refetchInterval: 1000, }); diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 7222b00..9c009cd 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -21,7 +21,7 @@ function Navbar() { Главная clsx( "2xl:p-[1.493vw] 2xl:py-[1.111vw] px-[21.5px] py-4 transition-colors flex 2xl:gap-[0.556vw] gap-2 items-center text-[#7D7D7D] hover:text-[#7B60F3]", diff --git a/src/components/Select.tsx b/src/components/Select.tsx index 31291cd..ae196a5 100644 --- a/src/components/Select.tsx +++ b/src/components/Select.tsx @@ -51,7 +51,7 @@ export default function Select({ options, onChange }: Props) {
{isOpen && ( -
+
{options.map((option) => ( { setModal(); setPosition("right"); }} > -
+

{session.owner.fullname}

diff --git a/src/components/SessionCommentItem.tsx b/src/components/SessionCommentItem.tsx index 8022da3..8f2ac6f 100644 --- a/src/components/SessionCommentItem.tsx +++ b/src/components/SessionCommentItem.tsx @@ -1,8 +1,8 @@ import { motion } from "motion/react"; -import { IComment } from "../types/IComments"; +import { Comment } from "../types/IComments"; import { format } from "date-fns"; -function SessionCommentItem({ comment }: { comment: IComment }) { +function SessionCommentItem({ comment }: { comment: Comment }) { return (
diff --git a/src/components/SessionComments.tsx b/src/components/SessionComments.tsx index 11376e9..efb1b8f 100644 --- a/src/components/SessionComments.tsx +++ b/src/components/SessionComments.tsx @@ -3,7 +3,7 @@ import SendIcon from "./icons/SendIcon"; import NewButton from "./NewButton"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import api from "../utils/api"; -import { IComment } from "../types/IComments"; +import { Comment } from "../types/IComments"; import SessionCommentItem from "./SessionCommentItem"; import { AnimatePresence } from "motion/react"; @@ -51,7 +51,7 @@ function SessionComments({ sessionId }: { sessionId: string }) { const { data: comments } = useQuery({ queryKey: ["sessions", "comments", sessionId], - queryFn: () => api.get(`comments/${sessionId}`).json(), + queryFn: () => api.get(`comments/${sessionId}`).json(), }); const { mutate: createComment } = useMutation({ diff --git a/src/components/TableSelector.tsx b/src/components/TableSelector.tsx index d211509..a58e984 100644 --- a/src/components/TableSelector.tsx +++ b/src/components/TableSelector.tsx @@ -1,11 +1,11 @@ import clsx from "clsx"; -import { IServer } from "../types/IServer"; +import { Server } from "../types/IServer"; import LightningIcon from "./icons/LightningIcon"; interface TableSelectorProps { - tables: IServer[]; - selectedTable: IServer | null; - onSelect: (table: IServer) => void; + tables: Server[]; + selectedTable: Server | null; + onSelect: (table: Server) => void; } function TableSelector({ diff --git a/src/components/modals/CreateSessionModal.tsx b/src/components/modals/CreateSessionModal.tsx index cf9f059..d5f0458 100644 --- a/src/components/modals/CreateSessionModal.tsx +++ b/src/components/modals/CreateSessionModal.tsx @@ -1,9 +1,9 @@ -import { IServer } from "../../types/IServer.ts"; +import { Server } from "../../types/IServer.ts"; import { useEffect, useRef, useState } from "react"; import { IApp } from "../../types/IApp.ts"; import api from "../../utils/api.ts"; -import { ISession } from "../../types/ISession.ts"; -import { IClient } from "../../types/IClient.ts"; +import { Session } from "../../types/ISession.ts"; +import { Client } from "../../types/IClient.ts"; import useModalStore from "../../stores/useModalStore.ts"; import TableSelector from "../TableSelector.tsx"; import NewInput from "../NewInput.tsx"; @@ -27,7 +27,7 @@ export default function CreateSessionModal({ targetServerId }: Props) { const { data: servers } = useQuery({ queryKey: ["servers"], - queryFn: () => api.get("servers?withLastSession=true").json(), + queryFn: () => api.get("servers?withLastSession=true").json(), refetchInterval: 1000, }); @@ -35,7 +35,7 @@ export default function CreateSessionModal({ targetServerId }: Props) { ? servers?.find((server) => server.id === targetServerId) || null : null; - const [selectedServer, setSelectedServer] = useState( + const [selectedServer, setSelectedServer] = useState( targetServer ); const [selectedApp, setSelectedApp] = useState(null); @@ -56,7 +56,7 @@ export default function CreateSessionModal({ targetServerId }: Props) { email, }, }) - .json(); + .json(); }, }); @@ -79,7 +79,7 @@ export default function CreateSessionModal({ targetServerId }: Props) { appId, }, }) - .json(), + .json(), onMutate: () => { queryClient.invalidateQueries({ queryKey: ["sessions"] }); queryClient.invalidateQueries({ queryKey: ["servers"] }); diff --git a/src/components/modals/CurrentSessionModal.tsx b/src/components/modals/CurrentSessionModal.tsx index d85a5d8..88e7fbb 100644 --- a/src/components/modals/CurrentSessionModal.tsx +++ b/src/components/modals/CurrentSessionModal.tsx @@ -5,10 +5,10 @@ import ChevronRightIcon from "../icons/ChevronRightIcon"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import api from "../../utils/api"; import useModalStore from "../../stores/useModalStore"; -import { ISession } from "../../types/ISession"; +import { Session } from "../../types/ISession"; import { useEffect, useState } from "react"; -function CurrentSessionModal({ session }: { session: ISession }) { +function CurrentSessionModal({ session }: { session: Session }) { const queryClient = useQueryClient(); const { setModal } = useModalStore(); @@ -86,7 +86,7 @@ function CurrentSessionModal({ session }: { session: ISession }) {

Клиент

-

{session.client.name}

+

{session.client.fullname}

{!session.client.email && ( diff --git a/src/components/modals/EditTableModal.tsx b/src/components/modals/EditTableModal.tsx index 2ce02b1..133bd41 100644 --- a/src/components/modals/EditTableModal.tsx +++ b/src/components/modals/EditTableModal.tsx @@ -2,12 +2,12 @@ import { useState } from "react"; import NewInput from "../NewInput"; import NewButton from "../NewButton"; import useModalStore from "../../stores/useModalStore"; -import { IServer } from "../../types/IServer"; +import { Server } from "../../types/IServer"; import { useQueryClient } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query"; import api from "../../utils/api"; -function EditTable({ table }: { table: IServer }) { +function EditTable({ table }: { table: Server }) { const [tableName, setTableName] = useState(table.name); const [tableDescription, setTableDescription] = useState(table.location); const { setModal } = useModalStore(); diff --git a/src/components/modals/SessionModal.tsx b/src/components/modals/SessionModal.tsx index 35981b2..7e97a69 100644 --- a/src/components/modals/SessionModal.tsx +++ b/src/components/modals/SessionModal.tsx @@ -1,4 +1,4 @@ -import { ISession } from "../../types/ISession"; +import { Session } from "../../types/ISession"; import { format } from "date-fns"; import { ru } from "date-fns/locale"; import getIntervalDuration from "../../utils/interval-duration"; @@ -11,7 +11,7 @@ import DownloadIcon from "../icons/DownloadIcon"; import ShareIcon from "../icons/ShareIcon"; import SessionComments from "../SessionComments"; -function SessionModal({ session }: { session: ISession }) { +function SessionModal({ session }: { session: Session }) { return (
diff --git a/src/pages/DashboardPage.tsx b/src/pages/DashboardPage.tsx index b11bd0d..b3cd7e5 100644 --- a/src/pages/DashboardPage.tsx +++ b/src/pages/DashboardPage.tsx @@ -1,10 +1,10 @@ import { useQuery } from "@tanstack/react-query"; import { IUser } from "../types/IUser"; import api from "../utils/api"; -import { IServer } from "../types/IServer"; +import { Server } from "../types/IServer"; import DesktopCard from "../components/DesktopCard"; import Badge from "../components/Badge"; -import { ISession } from "../types/ISession"; +import { Session } from "../types/ISession"; import SessionCard from "../components/SessionCard"; import NewButton from "../components/NewButton"; import ChevronRightIcon from "../components/icons/ChevronRightIcon"; @@ -17,7 +17,7 @@ function DashboardPage() { const { data: servers } = useQuery({ queryKey: ["servers"], - queryFn: () => api.get("servers?withLastSession=true").json(), + queryFn: () => api.get("servers?withLastSession=true").json(), enabled: !!me, refetchInterval: 1000, }); @@ -25,7 +25,7 @@ function DashboardPage() { const { data: sessions } = useQuery({ queryKey: ["last-sessions"], queryFn: () => - api.get("sessions", { searchParams: { limit: 5 } }).json(), + api.get("sessions", { searchParams: { limit: 5 } }).json(), enabled: !!me, }); diff --git a/src/pages/SessionsPage.tsx b/src/pages/SessionsPage.tsx new file mode 100644 index 0000000..369c414 --- /dev/null +++ b/src/pages/SessionsPage.tsx @@ -0,0 +1,124 @@ +import { useQuery } from "@tanstack/react-query"; +import api from "../utils/api"; +import { IUser } from "../types/IUser"; +import { Session } from "../types/ISession"; +import Input from "../components/Input"; +import Select from "../components/Select"; +import { useState } from "react"; +import { IApp } from "../types/IApp"; +import { useDebounce } from "@uidotdev/usehooks"; +import SessionCard from "../components/SessionCard"; +import { groupByCreatedAt } from "../utils/groupByCreatedAt"; +import { format, isToday } from "date-fns"; +import { ru } from "date-fns/locale"; + +function SessionsPage() { + const [search, setSearch] = useState(null); + const [managerId, setManagerId] = useState(null); + const [appId, setAppId] = useState(null); + + const debouncedSearch = useDebounce(search, 500); + + const { data: me } = useQuery({ + queryKey: ["me"], + queryFn: () => api.get("auth/me").json(), + }); + + const { data: managers } = useQuery({ + queryKey: ["managers"], + queryFn: () => api.get("users").json(), + enabled: !!me, + }); + + const { data: apps } = useQuery({ + queryKey: ["apps"], + queryFn: () => api.get("apps").json(), + enabled: !!me, + }); + + const { data: grouppedSessions } = useQuery({ + queryKey: ["sessions", managerId, appId, debouncedSearch], + queryFn: () => + api + .get( + `sessions?${managerId ? `ownerId=${managerId}` : ""}${ + appId ? `&appId=${appId}` : "" + }${debouncedSearch ? `&clientSearch=${debouncedSearch}` : ""}` + ) + .json(), + enabled: !!me, + select: groupByCreatedAt, + }); + + const { data: count } = useQuery({ + queryKey: ["sessions", "count", managerId, appId, debouncedSearch], + queryFn: () => + api + .get( + `sessions/count?${managerId ? `ownerId=${managerId}` : ""}${ + appId ? `&appId=${appId}` : "" + }${debouncedSearch ? `&clientSearch=${debouncedSearch}` : ""}` + ) + .json(), + enabled: !!me, + }); + + return ( +
+

Сеансы

+
+
+
+ setSearch(e.target.value)} + /> +
+ app.name) || []} + onChange={(option) => { + setAppId( + apps?.find((app) => app.name === option)?.id || null + ); + }} + /> +
+
+ {!!count && ( +

+ Найдено {count} сеансов +

+ )} +
+
+
+ {Object.entries(grouppedSessions || {}).map(([timestamp, sessions]) => ( +
+

+ {isToday(new Date(timestamp)) + ? "Сегодня" + : format(new Date(timestamp), "d MMMM", { locale: ru })} +

+
+ {sessions.map((session) => ( + + ))} +
+
+ ))} +
+
+ ); +} + +export default SessionsPage; diff --git a/src/queries/useQueryServers.ts b/src/queries/useQueryServers.ts index 525cf37..385bde0 100644 --- a/src/queries/useQueryServers.ts +++ b/src/queries/useQueryServers.ts @@ -1,10 +1,10 @@ -import { IServer } from "../types/IServer.ts"; +import { Server } from "../types/IServer.ts"; import api from "../utils/api.ts"; import { useQuery } from "@tanstack/react-query"; export default function useQueryServers() { return useQuery({ queryKey: ["servers"], - queryFn: () => api.get("servers").json(), + queryFn: () => api.get("servers").json(), }); } diff --git a/src/routes.tsx b/src/routes.tsx index 2da35f7..6190e59 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -2,6 +2,7 @@ import Layout from "./components/Layout"; import DashboardPage from "./pages/DashboardPage"; import LoginPage from "./pages/LoginPage"; import ProtectedPage from "./pages/ProtectedPage"; +import SessionsPage from "./pages/SessionsPage"; export default [ { @@ -15,6 +16,10 @@ export default [ index: true, element: , }, + { + path: "sessions", + element: , + }, ], }, ], diff --git a/src/types/IClient.ts b/src/types/IClient.ts index b087356..c109d10 100644 --- a/src/types/IClient.ts +++ b/src/types/IClient.ts @@ -1,4 +1,4 @@ -export interface IClient { +export interface Client { id: string; name: string; email: string; diff --git a/src/types/IComments.ts b/src/types/IComments.ts index 2c06abc..cff77e7 100644 --- a/src/types/IComments.ts +++ b/src/types/IComments.ts @@ -1,4 +1,4 @@ -export interface IComment { +export interface Comment { id: string; text: string; createdAt: Date; diff --git a/src/types/ICompany.ts b/src/types/ICompany.ts index 90a52ee..2e42d08 100644 --- a/src/types/ICompany.ts +++ b/src/types/ICompany.ts @@ -1,11 +1,11 @@ import { IApp } from "./IApp"; -import { IServer } from "./IServer"; +import { Server } from "./IServer"; import { IUser } from "./IUser"; export interface ICompany { id: string; name: string; apps?: IApp[]; - servers?: IServer[]; + servers?: Server[]; users?: IUser[]; } diff --git a/src/types/IServer.ts b/src/types/IServer.ts index 03b3b0f..6c83b2a 100644 --- a/src/types/IServer.ts +++ b/src/types/IServer.ts @@ -1,13 +1,13 @@ -import { IApp } from "./IApp"; -import { ISession } from "./ISession"; +import { IApp as App } from "./IApp"; +import { Session } from "./ISession"; -export interface IServer { +export interface Server { id: string; hostname: string; name: string; location: string; companyId: string; - sessions?: ISession[]; - apps?: IApp[]; + sessions?: Session[]; + apps?: App[]; status: "online" | "offline"; } diff --git a/src/types/ISession.ts b/src/types/ISession.ts index b974465..1d6a146 100644 --- a/src/types/ISession.ts +++ b/src/types/ISession.ts @@ -1,21 +1,21 @@ -import { IApp } from "./IApp"; -import { IComment } from "./IComments"; -import { IOwner } from "./IOwner"; -import { IServer } from "./IServer"; -import { IUser } from "./IUser"; +import { IApp as App } from "./IApp"; +import { Comment } from "./IComments"; +import { IOwner as Owner } from "./IOwner"; +import { Server } from "./IServer"; +import { Client } from "./IClient"; -export interface ISession { +export interface Session { id: string; ownerId: string; serverId: string; clientId: string; companyId: string; - comments: IComment[]; + comments: Comment[]; status: "starting" | "started" | "restarted" | "ending" | "ended"; - server: IServer; - client: IUser; - app: IApp; - owner: IOwner; + server: Server; + client: Client; + app: App; + owner: Owner; createdAt: Date; updatedAt: Date; } diff --git a/src/types/IUser.ts b/src/types/IUser.ts index 1a641e3..ad0b0c4 100644 --- a/src/types/IUser.ts +++ b/src/types/IUser.ts @@ -3,7 +3,7 @@ import { ICompany } from "./ICompany"; export interface IUser { id: string; email: string; - name: string; + fullname: string; companyId: string; company?: ICompany; } diff --git a/src/utils/groupByCreatedAt.ts b/src/utils/groupByCreatedAt.ts new file mode 100644 index 0000000..a36f652 --- /dev/null +++ b/src/utils/groupByCreatedAt.ts @@ -0,0 +1,8 @@ +export function groupByCreatedAt(items: T[]) { + return items.reduce((acc, session) => { + const date = session.createdAt.toString().split("T")[0]; + acc[date] = acc[date] || []; + acc[date].push(session); + return acc; + }, {} as Record); +} diff --git a/src/utils/interval-duration.tsx b/src/utils/interval-duration.ts similarity index 100% rename from src/utils/interval-duration.tsx rename to src/utils/interval-duration.ts