first commit

This commit is contained in:
2023-10-12 18:29:47 +05:00
commit 826ac1f621
64 changed files with 5159 additions and 0 deletions
+370
View File
@@ -0,0 +1,370 @@
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useEffect, useState } from "react";
import Card from "../components/Card";
import EmptyCard from "../components/EmptyCard";
import TabButton from "../components/TabButton";
import BurgerIcon from "../components/icons/BurgerIcon";
import ChevronLeftIcon from "../components/icons/ChevronLeftIcon";
import ChevronRightIcon from "../components/icons/ChevronRightIcon";
import api from "../utils/api";
import useAuthStore from "../stores/useAuthStore";
import {
eachMinuteOfInterval,
format,
parse,
addDays,
subDays,
} from "date-fns";
import Button from "../components/Button";
import { ru } from "date-fns/locale";
import { Transition } from "react-transition-group";
import SpinnerIcon from "../components/icons/SpinnerIcon";
function DashboardPage() {
const [user, setAccessToken] = useAuthStore((state) => [
state.user,
state.setAccessToken,
]);
const [company, setCompany] = useState<{ [key: string]: any }>();
const [managers, setManagers] = useState<any[]>();
const [builds, setBuilds] = useState<any[]>();
const [selectedBuild, setSelectedBuild] = useState<{ [key: string]: any }>();
const [scheduledSessions, setScheduledSessions] = useState<any[]>();
const [generatedScheduledSessions, setGeneratedScheduledSessions] =
useState<any[][]>();
const [selectedDate, setSelectedDate] = useState(new Date());
const startDateTime = parse("10:00", "HH:mm", selectedDate);
const endDateTime = parse("20:00", "HH:mm", selectedDate);
const dateTimes: Date[] = eachMinuteOfInterval(
{
start: startDateTime,
end: endDateTime,
},
{ step: 60 }
);
const [currentTime, setCurrentTime] = useState<string>(
format(new Date(), "HH:mm")
);
const [isLoadingScheduledSessions, setIsLoadingScheduledSessions] =
useState(true);
// const [selectedDate, setSelectedDate] = useState(
// format(new Date(), "d MMMM, yyyy", { locale: ru })
// );
function selectNextDay() {
setSelectedDate((prev) => addDays(prev, 1));
}
function selectPrevDay() {
setSelectedDate((prev) => subDays(prev, 1));
}
function selectCurrentDate() {
setSelectedDate(new Date());
}
async function getCompany() {
if (!user) {
console.log("No User", user);
return;
}
try {
const result: any = await api.get(`companies/${user.companyId}`).json();
console.log("getCompany result: ", result);
setCompany(result);
} catch (error) {
if (error instanceof Error) {
console.log("Error: ", error.message);
}
}
}
async function getBuilds() {
if (!user) {
console.log("No User", user);
return;
}
try {
const result: any = await api
.get(`companies/${user.companyId}/builds`)
.json();
console.log("getBuilds result: ", result);
setBuilds(result);
} catch (error) {
if (error instanceof Error) {
console.log("Error: ", error.message);
}
}
}
async function getManagers() {
if (!company) {
console.log("No Managers", managers);
return;
}
try {
const result: any = await api.get(`companies/${company.id}/users`).json();
console.log("getManagers result: ", result);
setManagers(result);
} catch (error) {
if (error instanceof Error) {
console.log("Error: ", error.message);
}
}
}
async function getScheduledSessions() {
if (!company) {
console.log("No ScheduledSessions");
return;
}
console.log("selectedBuild", selectedBuild);
if (!selectedBuild || !selectedBuild.id) {
console.log("No selectedBuild");
return;
}
setIsLoadingScheduledSessions(true);
try {
const result: any = await api
.get(
`companies/${company.id}/builds/${
selectedBuild.id
}/scheduled_sessions?date=${selectedDate.toISOString()}`
)
.json();
console.log("getScheduledSessions result: ", result);
setScheduledSessions(result);
setIsLoadingScheduledSessions(false);
} catch (error) {
setIsLoadingScheduledSessions(false);
if (error instanceof Error) {
console.log("Error: ", error.message);
}
}
}
function logout() {
setAccessToken(null);
}
function generateScheduledSessions() {
if (!scheduledSessions || !selectedBuild) return;
const arr: any[][] = [];
dateTimes.forEach((dateTime) => {
const arr2 = [];
const findedSessionsCount = scheduledSessions.filter(
(session) => session.startAt === dateTime.toISOString()
);
arr2.push(dateTime);
for (let i = 0; i < selectedBuild.sessionLimit; i++) {
if (findedSessionsCount[i]) {
arr2.push(findedSessionsCount[i]);
} else {
arr2.push({});
}
}
arr.push(arr2);
});
setGeneratedScheduledSessions(arr);
}
useEffect(() => {
getCompany();
const interval = setInterval(() => {
setCurrentTime(format(new Date(), "HH:mm"));
}, 1000);
return () => {
clearInterval(interval);
};
}, []);
useEffect(() => {
if (!company) return;
getBuilds();
}, [company]);
useEffect(() => {
if (!builds) return;
setSelectedBuild(builds[0]);
getManagers();
}, [builds]);
useEffect(() => {
if (!managers || !selectedDate || !selectedBuild) return;
getScheduledSessions();
console.log(selectedBuild);
}, [managers, selectedDate, selectedBuild]);
useEffect(() => {
if (!scheduledSessions) return;
generateScheduledSessions();
console.log(scheduledSessions);
}, [scheduledSessions]);
return (
<div className="main h-screen flex">
<div className="left flex flex-col w-full">
<div className="flex bg-[#F0F1F2] ">
<button
onClick={() => alert("Чё тыкаешь? В разработке еще!!")}
className="p-3 transition-colors hover:bg-neutral-200"
>
<BurgerIcon />
</button>
{builds?.map((build) => (
<TabButton
key={build.id}
handleClick={() => setSelectedBuild(build)}
active={selectedBuild && build.id === selectedBuild.id}
>
{build.name}
</TabButton>
))}
</div>
<div className="flex justify-between items-center px-4 py-2 h-12 border-r border-b border-[#DAE0E5]">
<p className="text-sm font-semibold">Расписание</p>
<div className="flex items-center gap-4">
<p className="text-sm font-semibold">
{format(selectedDate, "PPPP", { locale: ru })}
</p>
<div className="flex gap-1">
<Button
handleClick={selectPrevDay}
icon={<ChevronLeftIcon />}
color="secondary"
/>
<Button
handleClick={selectNextDay}
icon={<ChevronRightIcon />}
color="secondary"
/>
</div>
<Button handleClick={selectCurrentDate}>Сегодня</Button>
</div>
</div>
<div className="flex bg-[#F2F2F2]">
<div className="w-[84px] h-[40px] flex justify-center items-center text-sm font-semibold bg-white border-r border-b border-[#DAE0E5]">
{currentTime}
</div>
{selectedBuild &&
[...Array(selectedBuild.sessionLimit)].map((_, index) => (
<div key={index}>
<div className="w-[264px] h-[40px] px-3 flex items-center text-sm font-semibold bg-[#F0F1F2] border-r border-b border-[#DAE0E5]">
Слот {index + 1}
</div>
</div>
))}
</div>
<div
className={`overflow-y-auto overflow-x-hidden flex-1 bg-[#F2F2F2] border-r border-[#DAE0E5]`}
>
<Transition
in={isLoadingScheduledSessions}
timeout={150}
mountOnEnter
unmountOnExit
>
{(state) => (
<div
className={`fixed z-10 top-0 left-0 w-full h-full bg-black bg-opacity-80 transition-opacity flex justify-center items-center text-white ${state}`}
>
<SpinnerIcon />
</div>
)}
</Transition>
{!isLoadingScheduledSessions &&
generatedScheduledSessions?.map(
(generatedScheduledSession, index) => (
<div key={index} className="flex">
{generatedScheduledSession.map((scheduledSession, index2) => {
if (index2 === 0) {
return (
<div
key={index2}
className="w-[84px] h-[128px] flex justify-center items-center text-sm font-semibold bg-[#F0F1F2] border-r border-b border-[#DAE0E5]"
>
{format(scheduledSession, "HH:mm")}
</div>
);
} else {
if (Object.keys(scheduledSession).length) {
return (
<Card
key={index2}
client={{
name: scheduledSession.clientName,
phone: scheduledSession.clientPhone,
email: scheduledSession.clientEmail,
}}
manager={managers?.find(
(manager) =>
manager.id === scheduledSession.userId
)}
/>
);
} else {
return <EmptyCard key={index2} />;
}
}
})}
</div>
)
)}
</div>
</div>
<div className="right w-[320px] flex flex-col">
<div className="min-h-[48px] bg-[#F0F1F2] flex justify-between items-center">
<p className="text-xs font-semibold px-3 text-green-600">
{company?.name}
</p>
<p className="text-xs font-semibold px-3">{user?.username}</p>
<TabButton handleClick={logout} className="text-red-600">
Выйти
</TabButton>
</div>
<div className="flex gap-2 h-[48px] border-b border-[#DAE0E5]">
<button className="p-4 text-sm font-semibold">Параметры</button>
<button
disabled
className="p-4 text-sm font-semibold disabled:text-[#77828C]"
>
Статистика
</button>
</div>
<div className="overflow-y-auto overflow-x-hidden">
<p className="p-4">...</p>
</div>
</div>
</div>
);
}
export default DashboardPage;
+15
View File
@@ -0,0 +1,15 @@
import { Link } from "react-router-dom";
import Button from "../components/Button";
function HomePage() {
return (
<div className="p-8 flex flex-col gap-4 w-fit">
<p>HomePage</p>
<Link to="/login">
<Button type="button">Login</Button>
</Link>
</div>
);
}
export default HomePage;
+102
View File
@@ -0,0 +1,102 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useRef, useState } from "react";
import ky from "ky";
import ReCAPTCHA from "react-google-recaptcha";
import Form from "../components/Form";
import Label from "../components/Label";
import Input from "../components/Input";
import Button from "../components/Button";
import { Link, useNavigate } from "react-router-dom";
import useAuthStore from "../stores/useAuthStore";
function LoginPage() {
const [username, setUsername] = useState<string>();
const [password, setPassword] = useState<string>();
const recaptchaRef = useRef(null);
const [setAccessToken, setUser] = useAuthStore((state) => [
state.setAccessToken,
state.setUser,
]);
const [isLoading, setisLoading] = useState<boolean>(false);
const navigate = useNavigate();
async function login() {
setisLoading(true);
const result: any = await ky
.post(import.meta.env.VITE_SERVER_URL + "/login", {
json: {
username,
password,
},
})
.json();
if (result.error) {
alert(result.error);
return;
}
setAccessToken(result.accessToken);
setUser(result.user);
navigate("/dashboard");
setTimeout(() => {
setisLoading(false);
}, 3000);
}
return (
<div className="min-h-screen flex flex-col justify-center items-center p-8 flex-1 ">
<div className=" bg-white rounded-lg shadow-md overflow-hidden">
<div className="flex flex-col gap-6 p-12">
<p className="text-2xl font-semibold">Вход</p>
<Form handleSubmit={login}>
<div className="flex flex-col gap-1">
<Label value="Email" />
<Input
type="email"
placeholder="example@gmail.com"
autoFocus
required
handleChange={(value) => setUsername(value)}
/>
</div>
<div className="flex flex-col gap-1">
<Label value="Пароль" />
<Input
type="password"
placeholder="********"
required
handleChange={(value) => setPassword(value)}
/>
</div>
<ReCAPTCHA
ref={recaptchaRef}
sitekey="6LdKPH4oAAAAAM8cyMoCkmNvbnBbe2UIrwRwQ425"
className="mt-3"
/>
<Button
type="submit"
size="medium"
className="mt-10"
loading={isLoading}
>
Войти
</Button>
</Form>
<div className="self-center flex gap-6">
<Link to="/registration" className="text-xs text-[#49A1F5]">
Нет аккаунта?
</Link>
{/* <Link to="" className="text-xs text-[#49A1F5]">
Забыли пароль?
</Link> */}
</div>
</div>
</div>
</div>
);
}
export default LoginPage;
@@ -0,0 +1,35 @@
/* eslint-disable no-irregular-whitespace */
import { Link } from "react-router-dom";
import Button from "../components/Button";
function RegistrationCompanyPage() {
return (
<div className="min-h-screen flex flex-col justify-center items-center p-8 flex-1">
<div className=" bg-white rounded-lg shadow-md overflow-hidden">
<div className="flex flex-col gap-6 p-12 w-[528px]">
<p className="text-2xl font-semibold">Вы представитель компании</p>
<p>
Для получения данных для входа обратитесь в компанию GRAFF
interactive любым удобным для вас способом
</p>
<div className="flex gap-6">
<a href="tel:88007700067" className="text-blue-500">
8 800 770 00 67
</a>
<a href="mailto:support@graff.tech" className="text-blue-500">
support@graff.tech
</a>
</div>
<Link to="/login" className="mt-4">
<Button size="medium">На главную</Button>
</Link>
</div>
</div>
</div>
);
}
export default RegistrationCompanyPage;
@@ -0,0 +1,26 @@
/* eslint-disable no-irregular-whitespace */
import { Link } from "react-router-dom";
import Button from "../components/Button";
function RegistrationManagerPage() {
return (
<div className="min-h-screen flex flex-col justify-center items-center p-8 flex-1">
<div className=" bg-white rounded-lg shadow-md overflow-hidden">
<div className="flex flex-col gap-6 p-12 w-[528px]">
<p className="text-2xl font-semibold">Получение данных для входа</p>
<p>
Чтобы получить данные для входа обратитесь к представителю вашей
организации, для подключения вас к системе.
</p>
<Link to="/login" className="mt-4">
<Button size="medium">На главную</Button>
</Link>
</div>
</div>
</div>
);
}
export default RegistrationManagerPage;
+35
View File
@@ -0,0 +1,35 @@
import { Link } from "react-router-dom";
import Button from "../components/Button";
function RegistrationPage() {
return (
<div className="min-h-screen flex flex-col justify-center items-center p-8 flex-1">
<div className=" bg-white rounded-lg shadow-md overflow-hidden">
<div className="flex flex-col gap-6 p-12 w-[528px]">
<p className="text-2xl font-semibold">Получение данных для входа</p>
<div className="flex flex-col gap-4">
<Link to="company">
<button className="h-[56px] px-6 py-4 rounded-lg border border-[#DAE0E5] text-[#77828C] text-left w-full">
Я представитель компании
</button>
</Link>
<Link to="manager">
<button className="h-[56px] px-6 py-4 rounded-lg border border-[#DAE0E5] text-[#77828C] text-left w-full">
Я менеджер отдела продаж
</button>
</Link>
</div>
<Link to="/login">
<Button size="medium" color="secondary">
Назад
</Button>
</Link>
</div>
</div>
</div>
);
}
export default RegistrationPage;