Merge branch 'main' of http://192.168.1.163:3000/inmake/graff-mate-client
This commit is contained in:
@@ -8,7 +8,7 @@ function ClientCard({ client }: { client: IUser }) {
|
||||
<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">{client.name}</p>
|
||||
<p className="text-s font-medium">{client.fullname}</p>
|
||||
</div>
|
||||
<div className="flex gap-[0.556vw] items-center">
|
||||
{!client.email && (
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<ISession[]>(),
|
||||
.json<Session[]>(),
|
||||
refetchInterval: 1000,
|
||||
});
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ function Navbar() {
|
||||
<span className="2xl:text-[0.972vw] text-sm font-medium">Главная</span>
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to="/"
|
||||
to="sessions"
|
||||
className={({ isActive }) =>
|
||||
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]",
|
||||
|
||||
@@ -51,7 +51,7 @@ export default function Select({ options, onChange }: Props) {
|
||||
</div>
|
||||
</NewButton>
|
||||
{isOpen && (
|
||||
<div className="absolute top-full w-full bg-white rounded-[0.556vw] outline outline-black/10">
|
||||
<div className="absolute top-full w-full bg-white rounded-[0.556vw] outline outline-black/10 z-1">
|
||||
{options.map((option) => (
|
||||
<NewButton
|
||||
key={option}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import useModalStore from "../stores/useModalStore";
|
||||
import { ISession } from "../types/ISession";
|
||||
import { Session } from "../types/ISession";
|
||||
import SessionModal from "./modals/SessionModal";
|
||||
|
||||
function SessionCard({ session }: { session: ISession }) {
|
||||
function SessionCard({ session }: { session: Session }) {
|
||||
const { setModal, setPosition } = useModalStore();
|
||||
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"
|
||||
className="w-full h-[4.444vw] border-b-1 first:border-t-1 border-[#F6F6F6] flex py-[0.278vw] items-center gap-[0.556vw] cursor-pointer group"
|
||||
onClick={() => {
|
||||
setModal(<SessionModal session={session} />);
|
||||
setPosition("right");
|
||||
}}
|
||||
>
|
||||
<div className="rounded-xl w-full h-full flex items-center gap-[0.556vw] group-hover:bg-[#F6F6F6] transition-colors duration-200">
|
||||
<div className="rounded-[1.111vw] w-full h-full flex items-center gap-[0.556vw] group-hover:bg-[#F6F6F6] transition-colors duration-200 px-[1.111vw] py-[0.972vw]">
|
||||
<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>
|
||||
|
||||
@@ -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 (
|
||||
<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-xl w-full rounded-br-none">
|
||||
|
||||
@@ -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<IComment[]>(),
|
||||
queryFn: () => api.get(`comments/${sessionId}`).json<Comment[]>(),
|
||||
});
|
||||
|
||||
const { mutate: createComment } = useMutation({
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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<IServer[]>(),
|
||||
queryFn: () => api.get("servers?withLastSession=true").json<Server[]>(),
|
||||
refetchInterval: 1000,
|
||||
});
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function CreateSessionModal({ targetServerId }: Props) {
|
||||
? servers?.find((server) => server.id === targetServerId) || null
|
||||
: null;
|
||||
|
||||
const [selectedServer, setSelectedServer] = useState<IServer | null>(
|
||||
const [selectedServer, setSelectedServer] = useState<Server | null>(
|
||||
targetServer
|
||||
);
|
||||
const [selectedApp, setSelectedApp] = useState<IApp | null>(null);
|
||||
@@ -56,7 +56,7 @@ export default function CreateSessionModal({ targetServerId }: Props) {
|
||||
email,
|
||||
},
|
||||
})
|
||||
.json<IClient>();
|
||||
.json<Client>();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -79,7 +79,7 @@ export default function CreateSessionModal({ targetServerId }: Props) {
|
||||
appId,
|
||||
},
|
||||
})
|
||||
.json<ISession>(),
|
||||
.json<Session>(),
|
||||
onMutate: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["sessions"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["servers"] });
|
||||
|
||||
@@ -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 }) {
|
||||
<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">{session.client.name}</p>
|
||||
<p className="text-s font-medium">{session.client.fullname}</p>
|
||||
</div>
|
||||
<div className="flex gap-[0.556vw] items-center">
|
||||
{!session.client.email && (
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 (
|
||||
<div className="bg-[#FFFFFF] w-[49.722vw] rounded-4xl">
|
||||
<div className="w-full flex justify-center items-center h-[4.861vw]">
|
||||
|
||||
@@ -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<IServer[]>(),
|
||||
queryFn: () => api.get("servers?withLastSession=true").json<Server[]>(),
|
||||
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<ISession[]>(),
|
||||
api.get("sessions", { searchParams: { limit: 5 } }).json<Session[]>(),
|
||||
enabled: !!me,
|
||||
});
|
||||
|
||||
|
||||
@@ -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<string | null>(null);
|
||||
const [managerId, setManagerId] = useState<string | null>(null);
|
||||
const [appId, setAppId] = useState<string | null>(null);
|
||||
|
||||
const debouncedSearch = useDebounce(search, 500);
|
||||
|
||||
const { data: me } = useQuery({
|
||||
queryKey: ["me"],
|
||||
queryFn: () => api.get("auth/me").json<IUser>(),
|
||||
});
|
||||
|
||||
const { data: managers } = useQuery({
|
||||
queryKey: ["managers"],
|
||||
queryFn: () => api.get("users").json<IUser[]>(),
|
||||
enabled: !!me,
|
||||
});
|
||||
|
||||
const { data: apps } = useQuery({
|
||||
queryKey: ["apps"],
|
||||
queryFn: () => api.get("apps").json<IApp[]>(),
|
||||
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<Session[]>(),
|
||||
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<number>(),
|
||||
enabled: !!me,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="py-[1.667vw] flex flex-col gap-[1.667vw]">
|
||||
<h1 className="title-l font-medium">Сеансы</h1>
|
||||
<div className="p-[1.389vw] rounded-[2.222vw] shadow-[0_4px_40px_0_rgba(15,16,17,0.05),0_2px_2px_0_rgba(15,16,17,0.05)] w-full">
|
||||
<div className="space-y-[1.111vw]">
|
||||
<div className="flex flex-col gap-[0.556vw]">
|
||||
<Input
|
||||
placeholder="Поиск по имени клиента"
|
||||
value={search || ""}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
<div className="flex gap-[0.556vw]">
|
||||
<Select
|
||||
options={managers?.map((manager) => manager.fullname) || []}
|
||||
onChange={(option) => {
|
||||
setManagerId(
|
||||
managers?.find((manager) => manager.fullname === option)
|
||||
?.id || null
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
options={apps?.map((app) => app.name) || []}
|
||||
onChange={(option) => {
|
||||
setAppId(
|
||||
apps?.find((app) => app.name === option)?.id || null
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{!!count && (
|
||||
<p className="caption-m font-medium opacity-40">
|
||||
Найдено {count} сеансов
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-[1.667vw]">
|
||||
{Object.entries(grouppedSessions || {}).map(([timestamp, sessions]) => (
|
||||
<div key={timestamp} className="space-y-[0.833vw]">
|
||||
<p className="caption-m font-medium opacity-40">
|
||||
{isToday(new Date(timestamp))
|
||||
? "Сегодня"
|
||||
: format(new Date(timestamp), "d MMMM", { locale: ru })}
|
||||
</p>
|
||||
<div className="space-y-[0.278vw]">
|
||||
{sessions.map((session) => (
|
||||
<SessionCard key={session.id} session={session} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SessionsPage;
|
||||
@@ -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<IServer[]>(),
|
||||
queryFn: () => api.get("servers").json<Server[]>(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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: <DashboardPage />,
|
||||
},
|
||||
{
|
||||
path: "sessions",
|
||||
element: <SessionsPage />,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface IClient {
|
||||
export interface Client {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface IComment {
|
||||
export interface Comment {
|
||||
id: string;
|
||||
text: string;
|
||||
createdAt: Date;
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
+11
-11
@@ -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;
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { ICompany } from "./ICompany";
|
||||
export interface IUser {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
fullname: string;
|
||||
companyId: string;
|
||||
company?: ICompany;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export function groupByCreatedAt<T extends { createdAt: Date }>(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<string, T[]>);
|
||||
}
|
||||
Reference in New Issue
Block a user