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 f4016f7..beac39c 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 ChevronRightIcon from "./icons/ChevronRightIcon";
import { motion } from "motion/react";
@@ -11,7 +11,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 894584b..c10cd41 100644
--- a/src/components/modals/CurrentSessionModal.tsx
+++ b/src/components/modals/CurrentSessionModal.tsx
@@ -3,12 +3,13 @@ import FlashIcon from "../icons/FlashIcon";
import NewButton from "../NewButton";
import ChevronRightIcon from "../icons/ChevronRightIcon";
import useModalStore from "../../stores/useModalStore";
-import { ISession } from "../../types/ISession";
+import { Session } from "../../types/ISession";
import { useEffect, useState } from "react";
import EndSessionModal from "./EndSessionModal";
-function CurrentSessionModal({ session }: { session: ISession }) {
+function CurrentSessionModal({ session }: { session: Session }) {
// const queryClient = useQueryClient();
+
const { setModal } = useModalStore();
// const { mutate: endSession } = useMutation({
@@ -85,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)}
+ />
+
+
+
+ {!!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