Merge branch 'main' of http://192.168.1.163:3000/inmake/graff-mate-client
This commit is contained in:
@@ -1,19 +1,22 @@
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import SpinIcon from "./icons/SpinIcon";
|
||||||
|
|
||||||
interface NewInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
interface NewInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
isError?: boolean;
|
isError?: boolean;
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
|
isLoading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Input({
|
function Input({
|
||||||
placeholder,
|
placeholder,
|
||||||
isError,
|
isError,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
|
isLoading,
|
||||||
...props
|
...props
|
||||||
}: NewInputProps) {
|
}: NewInputProps) {
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className={clsx("relative", props.disabled && "opacity-40")}>
|
||||||
<input
|
<input
|
||||||
{...props}
|
{...props}
|
||||||
placeholder=""
|
placeholder=""
|
||||||
@@ -30,7 +33,7 @@ function Input({
|
|||||||
peer-focus:caption-xs peer-focus:top-[0.556vw] peer-focus:translate-y-0
|
peer-focus:caption-xs peer-focus:top-[0.556vw] peer-focus:translate-y-0
|
||||||
peer-[:not(:placeholder-shown)]:caption-xs peer-[:not(:placeholder-shown)]:top-[0.556vw] peer-[:not(:placeholder-shown)]:translate-y-0"
|
peer-[:not(:placeholder-shown)]:caption-xs peer-[:not(:placeholder-shown)]:top-[0.556vw] peer-[:not(:placeholder-shown)]:translate-y-0"
|
||||||
>
|
>
|
||||||
{placeholder}
|
{placeholder + (props.required ? "*" : "")}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{isError && (
|
{isError && (
|
||||||
@@ -38,6 +41,11 @@ function Input({
|
|||||||
{errorMessage}
|
{errorMessage}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
{isLoading && (
|
||||||
|
<div className="size-[1.389vw] text-[#7B60F3] animate-spin z-1 absolute right-[1.111vw] top-1/2 -translate-y-1/2">
|
||||||
|
<SpinIcon />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,9 +45,9 @@ function MultySelect<T extends { name: string; id: string }>({
|
|||||||
const isItemSelected = selectedValues.some((val) => val.id === item.id);
|
const isItemSelected = selectedValues.some((val) => val.id === item.id);
|
||||||
|
|
||||||
if (isItemSelected) {
|
if (isItemSelected) {
|
||||||
setSelectedValues(selectedValues.filter((value) => value.id !== item.id));
|
setSelectedValues((prev) => prev.filter((value) => value.id !== item.id));
|
||||||
} else {
|
} else {
|
||||||
setSelectedValues([...selectedValues, item]);
|
setSelectedValues((prev) => [...prev, item]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -59,9 +59,7 @@ function MultySelect<T extends { name: string; id: string }>({
|
|||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"flex items-center justify-between px-[1.111vw] py-[1.285vw] hover:bg-[#F0F0F0] rounded-[0.833vw] cursor-pointer",
|
"flex items-center justify-between px-[1.111vw] py-[1.285vw] hover:bg-[#F0F0F0] rounded-[0.833vw] cursor-pointer",
|
||||||
isSelectVisible
|
isSelectVisible && "!bg-[#E1DEFC] !text-[#7B60F3] hover:bg-[#E1DEFC]"
|
||||||
? "!bg-[#E1DEFC] !text-[#7B60F3] hover:bg-[#E1DEFC]"
|
|
||||||
: "text-[#141414]"
|
|
||||||
)}
|
)}
|
||||||
onClick={() => setIsSelectVisible(!isSelectVisible)}
|
onClick={() => setIsSelectVisible(!isSelectVisible)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { IApp } from "../types/App";
|
import { App } from "../types/App";
|
||||||
import ChevronLeftIcon from "./icons/ChevronLeftIcon";
|
import ChevronLeftIcon from "./icons/ChevronLeftIcon";
|
||||||
import CloseIcon from "./icons/CloseIcon";
|
import CloseIcon from "./icons/CloseIcon";
|
||||||
import LightningIcon from "./icons/LightningIcon";
|
import LightningIcon from "./icons/LightningIcon";
|
||||||
@@ -8,10 +8,10 @@ import CheckIcon from "./icons/CheckIcon";
|
|||||||
import ChevronDownIcon from "./icons/ChevronDownIcon";
|
import ChevronDownIcon from "./icons/ChevronDownIcon";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
projects: IApp[];
|
projects: App[];
|
||||||
selectedProject: IApp | null;
|
selectedProject: App | null;
|
||||||
setSelectedProject: (project: IApp | null) => void;
|
setSelectedProject: (project: App | null) => void;
|
||||||
activeProject: IApp | null;
|
activeProject: App | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProjectSelector({
|
function ProjectSelector({
|
||||||
@@ -26,7 +26,7 @@ function ProjectSelector({
|
|||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
const [pointedProject, setPointedProject] = useState<IApp | null>(null);
|
const [pointedProject, setPointedProject] = useState<App | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPointedProject(selectedProject);
|
setPointedProject(selectedProject);
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ function SpinIcon() {
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
background:
|
|
||||||
"conic-gradient(from 90deg,rgba(255,255,255,.0195) 0deg,#fff 314.826deg,rgba(255,255,255,0) 353.741deg,rgba(255,255,255,.0195) 360deg)",
|
|
||||||
height: "100%",
|
height: "100%",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Server } from "../../types/Server.ts";
|
import { Server } from "../../types/Server.ts";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { IApp } from "../../types/App.ts";
|
import { App } from "../../types/App.ts";
|
||||||
import api from "../../utils/api.ts";
|
import api from "../../utils/api.ts";
|
||||||
import { Session } from "../../types/Session.ts";
|
import { Session } from "../../types/Session.ts";
|
||||||
import { Client } from "../../types/Client.ts";
|
import { Client } from "../../types/Client.ts";
|
||||||
@@ -11,6 +11,8 @@ import StartSessionIcon from "../icons/StartSessionIcon.tsx";
|
|||||||
import Button from "../Button.tsx";
|
import Button from "../Button.tsx";
|
||||||
import ProjectSelector from "../ProjectSelector.tsx";
|
import ProjectSelector from "../ProjectSelector.tsx";
|
||||||
import { useQueryClient, useMutation, useQuery } from "@tanstack/react-query";
|
import { useQueryClient, useMutation, useQuery } from "@tanstack/react-query";
|
||||||
|
import { useDebounce } from "@uidotdev/usehooks";
|
||||||
|
import { AnimatePresence, motion } from "motion/react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
targetServerId: string | null;
|
targetServerId: string | null;
|
||||||
@@ -38,7 +40,7 @@ export default function CreateSessionModal({ targetServerId }: Props) {
|
|||||||
const [selectedServer, setSelectedServer] = useState<Server | null>(
|
const [selectedServer, setSelectedServer] = useState<Server | null>(
|
||||||
targetServer
|
targetServer
|
||||||
);
|
);
|
||||||
const [selectedApp, setSelectedApp] = useState<IApp | null>(null);
|
const [selectedApp, setSelectedApp] = useState<App | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedApp(
|
setSelectedApp(
|
||||||
@@ -48,9 +50,32 @@ export default function CreateSessionModal({ targetServerId }: Props) {
|
|||||||
);
|
);
|
||||||
}, [selectedServer]);
|
}, [selectedServer]);
|
||||||
|
|
||||||
|
const debouncedPhone = useDebounce(phone, 500);
|
||||||
|
|
||||||
|
const { data, isLoading, error } = useQuery({
|
||||||
|
queryKey: ["get-user-by-phone", debouncedPhone],
|
||||||
|
queryFn: () =>
|
||||||
|
api
|
||||||
|
.get("clients/by-phone", {
|
||||||
|
searchParams: debouncedPhone ? { phone: debouncedPhone } : {},
|
||||||
|
})
|
||||||
|
.json<Client>(),
|
||||||
|
enabled: !!debouncedPhone,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!error && data) {
|
||||||
|
setName(data.name);
|
||||||
|
setEmail(data.email);
|
||||||
|
} else {
|
||||||
|
setName(null);
|
||||||
|
setEmail(null);
|
||||||
|
}
|
||||||
|
}, [data, error]);
|
||||||
|
|
||||||
const { mutate: createClient } = useMutation({
|
const { mutate: createClient } = useMutation({
|
||||||
mutationFn: () => {
|
mutationFn: () =>
|
||||||
return api
|
api
|
||||||
.post("clients", {
|
.post("clients", {
|
||||||
json: {
|
json: {
|
||||||
name,
|
name,
|
||||||
@@ -58,8 +83,7 @@ export default function CreateSessionModal({ targetServerId }: Props) {
|
|||||||
email,
|
email,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.json<Client>();
|
.json<Client>(),
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutate: createSession } = useMutation({
|
const { mutate: createSession } = useMutation({
|
||||||
@@ -158,7 +182,7 @@ export default function CreateSessionModal({ targetServerId }: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
className="relative rounded-[2.222vw] w-[25vw] min-h-[calc(100dvh-2.222vw)] bg-[#F0F0F0] flex flex-col overflow-hidden"
|
className="relative rounded-[2.222vw] w-[25vw] bg-[#F0F0F0] flex flex-col overflow-hidden"
|
||||||
onSubmit={handleClickCreateSession}
|
onSubmit={handleClickCreateSession}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
@@ -166,7 +190,7 @@ export default function CreateSessionModal({ targetServerId }: Props) {
|
|||||||
<p className="title-s font-medium">Новый сеанс</p>
|
<p className="title-s font-medium">Новый сеанс</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full h-[6.944vw] bg-[url(/images/Table.png)] bg-no-repeat bg-top bg-[length:9.306vw]" />
|
<div className="w-full h-[6.944vw] bg-[url(/images/Table.png)] bg-no-repeat bg-top bg-[length:9.306vw]" />
|
||||||
<div className="bg-white rounded-t-[2.222vw] p-[1.389vw] flex flex-col gap-y-[1.667vw] flex-1 overflow-y-auto">
|
<div className="bg-white rounded-t-[2.222vw] p-[1.389vw] flex flex-col gap-y-[1.667vw] flex-1moverflow-y-auto">
|
||||||
<TableSelector
|
<TableSelector
|
||||||
tables={servers || []}
|
tables={servers || []}
|
||||||
selectedTable={selectedServer}
|
selectedTable={selectedServer}
|
||||||
@@ -180,19 +204,40 @@ export default function CreateSessionModal({ targetServerId }: Props) {
|
|||||||
onChange={(e) => setPhone(e.target.value)}
|
onChange={(e) => setPhone(e.target.value)}
|
||||||
placeholder="Номер телефона"
|
placeholder="Номер телефона"
|
||||||
required
|
required
|
||||||
|
isLoading={isLoading}
|
||||||
/>
|
/>
|
||||||
<Input
|
<AnimatePresence>
|
||||||
value={name || ""}
|
{phone && (
|
||||||
onChange={(e) => setName(e.target.value)}
|
<>
|
||||||
placeholder="Имя"
|
<motion.div
|
||||||
required
|
initial={{ opacity: 0 }}
|
||||||
/>
|
animate={{ opacity: 1 }}
|
||||||
<Input
|
exit={{ opacity: 0 }}
|
||||||
type="email"
|
>
|
||||||
value={email || ""}
|
<Input
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
value={name || ""}
|
||||||
placeholder="Электронная почта"
|
disabled={isLoading}
|
||||||
/>
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
placeholder="Имя"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
disabled={isLoading}
|
||||||
|
type="email"
|
||||||
|
value={email || ""}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
placeholder="Электронная почта"
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-y-[0.833vw]">
|
<div className="flex flex-col gap-y-[0.833vw]">
|
||||||
@@ -232,7 +277,8 @@ export default function CreateSessionModal({ targetServerId }: Props) {
|
|||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={
|
disabled={
|
||||||
!ref.current?.checkValidity() ||
|
!phone ||
|
||||||
|
!name ||
|
||||||
!selectedServer ||
|
!selectedServer ||
|
||||||
!selectedApp ||
|
!selectedApp ||
|
||||||
servers?.find((server) => server.id === selectedServer?.id)
|
servers?.find((server) => server.id === selectedServer?.id)
|
||||||
@@ -240,7 +286,6 @@ export default function CreateSessionModal({ targetServerId }: Props) {
|
|||||||
}
|
}
|
||||||
variant="cta"
|
variant="cta"
|
||||||
size="large"
|
size="large"
|
||||||
className="sticky bottom-0"
|
|
||||||
>
|
>
|
||||||
<div className="size-[1.111vw]">
|
<div className="size-[1.111vw]">
|
||||||
<StartSessionIcon />
|
<StartSessionIcon />
|
||||||
|
|||||||
+2
-2
@@ -7,12 +7,12 @@ body {
|
|||||||
color: #141414;
|
color: #141414;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button:enabled {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:disabled {
|
button:disabled {
|
||||||
color: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
|
|||||||
+31
-24
@@ -1,3 +1,4 @@
|
|||||||
|
import api from "../utils/api";
|
||||||
import Button from "../components/Button";
|
import Button from "../components/Button";
|
||||||
import CloseIcon from "../components/icons/CloseIcon";
|
import CloseIcon from "../components/icons/CloseIcon";
|
||||||
import SpinIcon from "../components/icons/SpinIcon";
|
import SpinIcon from "../components/icons/SpinIcon";
|
||||||
@@ -5,25 +6,44 @@ import MultySelect from "../components/MultySelect";
|
|||||||
import SearchInput from "../components/SearchInput";
|
import SearchInput from "../components/SearchInput";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { IUser } from "../types/User";
|
import { User } from "../types/User";
|
||||||
import api from "../utils/api";
|
|
||||||
import { Client } from "../types/Client";
|
import { Client } from "../types/Client";
|
||||||
|
import { useDebounce } from "@uidotdev/usehooks";
|
||||||
import pluralize from "../utils/pluralize";
|
import pluralize from "../utils/pluralize";
|
||||||
|
|
||||||
function ClientsPage() {
|
function ClientsPage() {
|
||||||
const [limit, setLimit] = useState(10);
|
const [limit, setLimit] = useState(10);
|
||||||
const [search, setSearch] = useState<string | null>(null);
|
const [search, setSearch] = useState<string | null>(null);
|
||||||
|
|
||||||
// const debouncedSearch = useDebounce(search, 500);
|
const debouncedSearch = useDebounce(search, 500);
|
||||||
|
|
||||||
const { data: me } = useQuery({
|
const { data: me } = useQuery({
|
||||||
queryKey: ["me"],
|
queryKey: ["me"],
|
||||||
queryFn: () => api.get("auth/me").json<IUser>(),
|
queryFn: () => api.get("auth/me").json<User>(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: clients, isLoading } = useQuery({
|
const { data: clients, isLoading } = useQuery({
|
||||||
queryKey: ["clients"],
|
queryKey: ["clients", debouncedSearch],
|
||||||
queryFn: () => api.get("clients").json<Client[]>(),
|
queryFn: () =>
|
||||||
|
api
|
||||||
|
.get("clients", {
|
||||||
|
searchParams: debouncedSearch
|
||||||
|
? { search: debouncedSearch, limit }
|
||||||
|
: {},
|
||||||
|
})
|
||||||
|
.json<Client[]>(),
|
||||||
|
enabled: !!me,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: count } = useQuery({
|
||||||
|
queryKey: ["clients", "count", debouncedSearch],
|
||||||
|
queryFn: () =>
|
||||||
|
api
|
||||||
|
.get(`clients/count`, {
|
||||||
|
searchParams: debouncedSearch ? { search: debouncedSearch } : {},
|
||||||
|
})
|
||||||
|
.json<number>(),
|
||||||
|
|
||||||
enabled: !!me,
|
enabled: !!me,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -32,11 +52,11 @@ function ClientsPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className=" flex flex-col gap-[1.667vw]">
|
<div className="flex flex-col gap-[1.667vw] h-full">
|
||||||
<h1 className="title-l font-medium">Клиенты</h1>
|
<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="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="space-y-[1.111vw]">
|
||||||
<div className="flex flex-col gap-[0.556vw]">
|
<div className="flex flex-col gap-[0.556vw] h-full">
|
||||||
<SearchInput
|
<SearchInput
|
||||||
placeholder="Поиск клиентов"
|
placeholder="Поиск клиентов"
|
||||||
value={search || ""}
|
value={search || ""}
|
||||||
@@ -45,17 +65,7 @@ function ClientsPage() {
|
|||||||
/>
|
/>
|
||||||
<div className="flex gap-[0.556vw]">
|
<div className="flex gap-[0.556vw]">
|
||||||
<MultySelect
|
<MultySelect
|
||||||
data={[
|
data={[]}
|
||||||
{ name: "С бронью", id: "1" },
|
|
||||||
{
|
|
||||||
name: "С избранными лотами",
|
|
||||||
id: "2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Без отправленных КП",
|
|
||||||
id: "3",
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
isGrid={false}
|
isGrid={false}
|
||||||
placeholder={"Все встречи"}
|
placeholder={"Все встречи"}
|
||||||
resetTitle={"Все встречи"}
|
resetTitle={"Все встречи"}
|
||||||
@@ -72,10 +82,7 @@ function ClientsPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<p className="caption-m font-medium opacity-40">
|
<p className="caption-m font-medium opacity-40">
|
||||||
Найдено{" "}
|
Найдено {count ? pluralize(count, "клиент") : "0 клиентов"}
|
||||||
{clients?.length
|
|
||||||
? pluralize(clients?.length, "клиент")
|
|
||||||
: "0 клиентов"}
|
|
||||||
</p>
|
</p>
|
||||||
<button className="flex gap-[0.278vw] items-center" onClick={reset}>
|
<button className="flex gap-[0.278vw] items-center" onClick={reset}>
|
||||||
<div className="size-[1.111vw] text-[#7D7D7D]">
|
<div className="size-[1.111vw] text-[#7D7D7D]">
|
||||||
@@ -95,7 +102,7 @@ function ClientsPage() {
|
|||||||
</div>
|
</div>
|
||||||
) : clients?.length ? (
|
) : clients?.length ? (
|
||||||
clients?.map(({ name }) => (
|
clients?.map(({ name }) => (
|
||||||
<div key={name} className="space-y-[0.833vw]">
|
<div key={name} className="aspace-y-[0.833vw]">
|
||||||
<p className="caption-m font-medium opacity-40">{name}</p>
|
<p className="caption-m font-medium opacity-40">{name}</p>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { IUser } from "../types/User";
|
import { User } from "../types/User";
|
||||||
import api from "../utils/api";
|
import api from "../utils/api";
|
||||||
import { Server } from "../types/Server";
|
import { Server } from "../types/Server";
|
||||||
import DesktopCard from "../components/DesktopCard";
|
import DesktopCard from "../components/DesktopCard";
|
||||||
@@ -13,7 +13,7 @@ import { useNavigate } from "react-router";
|
|||||||
function DashboardPage() {
|
function DashboardPage() {
|
||||||
const { data: me } = useQuery({
|
const { data: me } = useQuery({
|
||||||
queryKey: ["me"],
|
queryKey: ["me"],
|
||||||
queryFn: () => api.get("auth/me").json<IUser>(),
|
queryFn: () => api.get("auth/me").json<User>(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: servers } = useQuery({
|
const { data: servers } = useQuery({
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ import { Navigate, Outlet } from "react-router";
|
|||||||
import useAuthStore from "../stores/useAuthStore";
|
import useAuthStore from "../stores/useAuthStore";
|
||||||
import api from "../utils/api";
|
import api from "../utils/api";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { IUser } from "../types/User";
|
import { User } from "../types/User";
|
||||||
|
|
||||||
function ProtectedPage() {
|
function ProtectedPage() {
|
||||||
const { token } = useAuthStore();
|
const { token } = useAuthStore();
|
||||||
|
|
||||||
const { data: user, isLoading } = useQuery({
|
const { data: user, isLoading } = useQuery({
|
||||||
queryKey: ["me"],
|
queryKey: ["me"],
|
||||||
queryFn: () => api.get("auth/me").json<IUser>(),
|
queryFn: () => api.get("auth/me").json<User>(),
|
||||||
enabled: !!token,
|
enabled: !!token,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import api from "../utils/api";
|
import api from "../utils/api";
|
||||||
import { IUser } from "../types/User";
|
import { User } from "../types/User";
|
||||||
import { Session } from "../types/Session";
|
import { Session } from "../types/Session";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { IApp } from "../types/App";
|
import { App } from "../types/App";
|
||||||
import { useDebounce } from "@uidotdev/usehooks";
|
import { useDebounce } from "@uidotdev/usehooks";
|
||||||
import SessionCard from "../components/SessionCard";
|
import SessionCard from "../components/SessionCard";
|
||||||
import { groupByCreatedAt } from "../utils/groupByCreatedAt";
|
import { groupByCreatedAt } from "../utils/groupByCreatedAt";
|
||||||
@@ -19,6 +19,7 @@ import pluralize from "../utils/pluralize";
|
|||||||
function SessionsPage() {
|
function SessionsPage() {
|
||||||
const [limit, setLimit] = useState(10);
|
const [limit, setLimit] = useState(10);
|
||||||
const [search, setSearch] = useState<string | null>(null);
|
const [search, setSearch] = useState<string | null>(null);
|
||||||
|
|
||||||
const [managerIds, setManagerIds] = useState<string[]>([]);
|
const [managerIds, setManagerIds] = useState<string[]>([]);
|
||||||
const [appIds, setAppIds] = useState<string[]>([]);
|
const [appIds, setAppIds] = useState<string[]>([]);
|
||||||
const [shouldReset, setShouldReset] = useState(false);
|
const [shouldReset, setShouldReset] = useState(false);
|
||||||
@@ -27,18 +28,18 @@ function SessionsPage() {
|
|||||||
|
|
||||||
const { data: me } = useQuery({
|
const { data: me } = useQuery({
|
||||||
queryKey: ["me"],
|
queryKey: ["me"],
|
||||||
queryFn: () => api.get("auth/me").json<IUser>(),
|
queryFn: () => api.get("auth/me").json<User>(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: managers } = useQuery({
|
const { data: managers } = useQuery({
|
||||||
queryKey: ["managers"],
|
queryKey: ["managers"],
|
||||||
queryFn: () => api.get("users").json<IUser[]>(),
|
queryFn: () => api.get("users").json<User[]>(),
|
||||||
enabled: !!me,
|
enabled: !!me,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: apps } = useQuery({
|
const { data: apps } = useQuery({
|
||||||
queryKey: ["apps"],
|
queryKey: ["apps"],
|
||||||
queryFn: () => api.get("apps").json<IApp[]>(),
|
queryFn: () => api.get("apps").json<App[]>(),
|
||||||
enabled: !!me,
|
enabled: !!me,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { IApp } from "../types/App.ts";
|
import { App } from "../types/App.ts";
|
||||||
import api from "../utils/api.ts";
|
import api from "../utils/api.ts";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
export default function useQueryApps() {
|
export default function useQueryApps() {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["apps"],
|
queryKey: ["apps"],
|
||||||
queryFn: () => api.get("apps").json<IApp[]>(),
|
queryFn: () => api.get("apps").json<App[]>(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
export interface IApp {
|
export interface App {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
fileName: string;
|
fileName: string;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { IApp } from "./App";
|
import { App } from "./App";
|
||||||
import { Server } from "./Server";
|
import { Server } from "./Server";
|
||||||
import { IUser } from "./User";
|
import { User } from "./User";
|
||||||
|
|
||||||
export interface ICompany {
|
export interface Company {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
apps?: IApp[];
|
apps?: App[];
|
||||||
servers?: Server[];
|
servers?: Server[];
|
||||||
users?: IUser[];
|
users?: User[];
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import { IApp as App } from "./App";
|
import { App as App } from "./App";
|
||||||
import { Session } from "./Session";
|
import { Session } from "./Session";
|
||||||
|
|
||||||
export interface Server {
|
export interface Server {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IApp as App } from "./App";
|
import { App as App } from "./App";
|
||||||
import { Comment } from "./Comment";
|
import { Comment } from "./Comment";
|
||||||
import { IOwner as Owner } from "./Owner";
|
import { IOwner as Owner } from "./Owner";
|
||||||
import { Server } from "./Server";
|
import { Server } from "./Server";
|
||||||
|
|||||||
+3
-3
@@ -1,9 +1,9 @@
|
|||||||
import { ICompany } from "./Company";
|
import { Company } from "./Company";
|
||||||
|
|
||||||
export interface IUser {
|
export interface User {
|
||||||
id: string;
|
id: string;
|
||||||
email: string;
|
email: string;
|
||||||
fullname: string;
|
fullname: string;
|
||||||
companyId: string;
|
companyId: string;
|
||||||
company?: ICompany;
|
company?: Company;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user