This commit is contained in:
2024-10-15 17:54:13 +05:00
parent ba69acf1ad
commit 45e972780a
11 changed files with 126 additions and 199 deletions
+35 -12
View File
@@ -1,14 +1,8 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { useEffect, useState } from "react"; import { ChangeEvent, useEffect, useState } from "react";
import Timeline from "./Timeline"; import Timeline from "./Timeline";
import { import { format, getDate, setDate, setHours, startOfDay } from "date-fns";
format,
getDate,
setDate,
setHours,
startOfDay,
} from "date-fns";
// import useEventStore from "../stores/useEventStore"; // import useEventStore from "../stores/useEventStore";
import Button from "./Button"; import Button from "./Button";
import CloseIcon from "./icons/CloseIcon"; import CloseIcon from "./icons/CloseIcon";
@@ -16,15 +10,17 @@ import Input from "./Input";
import Label from "./Label"; import Label from "./Label";
import api from "../utils/api"; import api from "../utils/api";
import useStore from "../stores/useStore"; import useStore from "../stores/useStore";
import Select2 from "./Select2";
import IScheduledSession from "../types/IScheduledSession";
interface Props { interface Props {
selectedDay: Date; selectedDay: Date;
slots: number; slots: number;
events: any[]; events: IScheduledSession[];
} }
function Schedule({ selectedDay, slots, events }: Props) { function Schedule({ selectedDay, slots, events }: Props) {
const { selectedBuild } = useStore(); const { company, builds, selectedBuild, setSelectedBuild } = useStore();
const [draftMode, setDraftMode] = useState<boolean>(false); const [draftMode, setDraftMode] = useState<boolean>(false);
const [slot, setSlot] = useState<number>(); const [slot, setSlot] = useState<number>();
const [startAt, setStartAt] = useState<Date>(); const [startAt, setStartAt] = useState<Date>();
@@ -77,6 +73,7 @@ function Schedule({ selectedDay, slots, events }: Props) {
await api await api
.post(`scheduled_sessions`, { .post(`scheduled_sessions`, {
json: { json: {
companyId: company!.id,
buildId: selectedBuild?.id, buildId: selectedBuild?.id,
slot, slot,
startAt, startAt,
@@ -97,9 +94,17 @@ function Schedule({ selectedDay, slots, events }: Props) {
// setModal(null); // setModal(null);
} }
useEffect(() => {
if (!builds) return;
setSelectedBuild(builds[0]);
console.log(builds);
}, [builds]);
useEffect(() => { useEffect(() => {
console.log("events", events); console.log("events", events);
}, []); }, [events]);
return ( return (
<div className="relative h-screen overflow-y-auto bg-[#F2F2F2] text-sm"> <div className="relative h-screen overflow-y-auto bg-[#F2F2F2] text-sm">
@@ -114,7 +119,7 @@ function Schedule({ selectedDay, slots, events }: Props) {
key={index} key={index}
className="border-r border-b border-[#DAE0E5] w-[264px] h-10 flex items-center pl-3 bg-[#F0F1F2]" className="border-r border-b border-[#DAE0E5] w-[264px] h-10 flex items-center pl-3 bg-[#F0F1F2]"
> >
<p className="font-semibold">Слот {index + 1}</p> <p className="font-semibold">Сервер {index + 1}</p>
</div> </div>
))} ))}
</div> </div>
@@ -166,6 +171,24 @@ function Schedule({ selectedDay, slots, events }: Props) {
<div className="px-4 space-y-2"> <div className="px-4 space-y-2">
<p className="font-semibold">Демонстрация</p> <p className="font-semibold">Демонстрация</p>
<div className=""> <div className="">
<div className="py-1 space-y-1 text-xs">
<p className="text-[#77828C]">Выберите ЖК</p>
<Select2
defaultValue={selectedBuild?.build}
options={builds.map((build) => ({
value: build.build,
text: build.name,
}))}
onChange={(e: ChangeEvent<HTMLSelectElement>) =>
setSelectedBuild(
builds.find(
(build) =>
build.build === e.target.selectedOptions[0].value
)!
)
}
/>
</div>
<div className="grid items-center grid-cols-2 gap-4 py-1 text-xs"> <div className="grid items-center grid-cols-2 gap-4 py-1 text-xs">
<p className="text-[#77828C]">Дата и время</p> <p className="text-[#77828C]">Дата и время</p>
<p className="">{format(startAt, "dd.MM.yyyy HH:mm")}</p> <p className="">{format(startAt, "dd.MM.yyyy HH:mm")}</p>
+34
View File
@@ -0,0 +1,34 @@
import { ChangeEvent } from "react";
interface IOption {
value: string;
text: string;
}
interface Props {
defaultValue?: string;
options: IOption[];
onChange: (e: ChangeEvent<HTMLSelectElement>) => void;
}
function Select2({ defaultValue, options, onChange }: Props) {
function handleChange(e: ChangeEvent<HTMLSelectElement>) {
onChange(e);
}
return (
<select
onChange={handleChange}
defaultValue={defaultValue}
className="px-2 py-2.5 outline-none rounded-lg border border-[#DAE0E5] focus:border-[#49A1F5] transition-colors text-sm w-full h-10"
>
{options.map((option, index) => (
<option key={index} value={option.value}>
{option.text}
</option>
))}
</select>
);
}
export default Select2;
+11 -5
View File
@@ -4,9 +4,11 @@ import { useState, MouseEvent, useEffect, useRef } from "react";
import TimelineSlot from "./TimelineSlot"; import TimelineSlot from "./TimelineSlot";
import { addMinutes, differenceInMinutes, format, startOfDay } from "date-fns"; import { addMinutes, differenceInMinutes, format, startOfDay } from "date-fns";
import Button from "./Button"; import Button from "./Button";
import IScheduledSession from "../types/IScheduledSession";
import useStore from "../stores/useStore";
interface Props { interface Props {
timelineEvents: any[]; timelineEvents: IScheduledSession[];
slot: number; slot: number;
draftMode: boolean; draftMode: boolean;
onChangeDraftMode: (draftMode: boolean) => void; onChangeDraftMode: (draftMode: boolean) => void;
@@ -33,6 +35,7 @@ function Timeline({
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const [startAt, setStartAt] = useState<string>(); const [startAt, setStartAt] = useState<string>();
const [duration, setDuration] = useState<number>(30); // min const [duration, setDuration] = useState<number>(30); // min
const { builds } = useStore();
function handleMouseDown(e: MouseEvent<HTMLDivElement>) { function handleMouseDown(e: MouseEvent<HTMLDivElement>) {
if (draftMode) return; if (draftMode) return;
@@ -168,10 +171,13 @@ function Timeline({
}} }}
> >
<div className="flex flex-col justify-between h-full"> <div className="flex flex-col justify-between h-full">
<p> <div className="space-y-1">
{format(new Date(event.startAt), "HH:mm")} -{" "} <p>
{format(new Date(event.endAt), "HH:mm")} {format(new Date(event.startAt), "HH:mm")} -{" "}
</p> {format(new Date(event.endAt), "HH:mm")}
</p>
<p>{builds.find((build) => build.id === event.buildId)?.name}</p>
</div>
<a <a
href={`https://stream.graff.tech/scheduled/${event.id}?admin=true`} href={`https://stream.graff.tech/scheduled/${event.id}?admin=true`}
target="_blank" target="_blank"
@@ -55,7 +55,7 @@ function CreateScheduledSessionModal({ buildId, startAt, duration }: Props) {
onSubmit={addSchesuledSession} onSubmit={addSchesuledSession}
> >
<div className="p-2 pl-6 flex justify-between items-center border-b border-[#DAE0E5]"> <div className="p-2 pl-6 flex justify-between items-center border-b border-[#DAE0E5]">
<p className="font-semibold text-sm">Запланировать демонстрацию</p> <p className="text-sm font-semibold">Запланировать демонстрацию</p>
<span className="text-[#77828C]"> <span className="text-[#77828C]">
<Button <Button
color="tertiary" color="tertiary"
@@ -68,18 +68,18 @@ function CreateScheduledSessionModal({ buildId, startAt, duration }: Props) {
<div className="px-6 py-8 space-y-8 border-b border-[#DAE0E5]"> <div className="px-6 py-8 space-y-8 border-b border-[#DAE0E5]">
<div className="flex gap-4"> <div className="flex gap-4">
<div className="w-[240px]"> <div className="w-[240px]">
<p className="font-semibold text-sm">Демонстрация</p> <p className="text-sm font-semibold">Демонстрация</p>
</div> </div>
<div className="w-[296px]"> <div className="w-[296px]">
<div className="grid grid-cols-2 gap-4 text-xs py-1"> <div className="grid grid-cols-2 gap-4 py-1 text-xs">
<p className="text-[#77828C]">Дата и время</p> <p className="text-[#77828C]">Дата и время</p>
<p className="">{format(startAt, "dd.MM.yyyy HH:mm")}</p> <p className="">{format(startAt, "dd.MM.yyyy HH:mm")}</p>
</div> </div>
<div className="grid grid-cols-2 gap-4 text-xs py-1"> <div className="grid grid-cols-2 gap-4 py-1 text-xs">
<p className="text-[#77828C]">Длительность сеанса</p> <p className="text-[#77828C]">Длительность сеанса</p>
<p className="">{duration} мин.</p> <p className="">{duration} мин.</p>
</div> </div>
{/* <div className="grid grid-cols-2 gap-4 text-xs py-1"> {/* <div className="grid grid-cols-2 gap-4 py-1 text-xs">
<p className="text-[#77828C]">Жилой комплекс</p> <p className="text-[#77828C]">Жилой комплекс</p>
<p className="">Название ЖК</p> <p className="">Название ЖК</p>
</div> */} </div> */}
@@ -90,7 +90,7 @@ function CreateScheduledSessionModal({ buildId, startAt, duration }: Props) {
<div className="w-[240px]"> <div className="w-[240px]">
<div className="flex flex-col justify-between h-full"> <div className="flex flex-col justify-between h-full">
<div className="space-y-2"> <div className="space-y-2">
<p className="font-semibold text-sm">Укажите данные клиента*</p> <p className="text-sm font-semibold">Укажите данные клиента*</p>
<p className="text-xs text-[#77828C]"> <p className="text-xs text-[#77828C]">
На указанный почтовый адрес будет отправлена информация На указанный почтовый адрес будет отправлена информация
необходимая для подключения необходимая для подключения
+23 -157
View File
@@ -1,15 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { import { format, addDays, subDays } from "date-fns";
eachMinuteOfInterval,
format,
parse,
addDays,
subDays,
isEqual,
} from "date-fns";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import TabButton from "../components/TabButton";
import api from "../utils/api"; import api from "../utils/api";
import { ru } from "date-fns/locale"; import { ru } from "date-fns/locale";
import useStore from "../stores/useStore"; import useStore from "../stores/useStore";
@@ -24,6 +16,7 @@ import ChevronRightIcon from "../components/icons/ChevronRightIcon";
import IUser from "../types/IUser"; import IUser from "../types/IUser";
import IError from "../types/IError"; import IError from "../types/IError";
import Schedule from "../components/Schedule"; import Schedule from "../components/Schedule";
import IScheduledSession from "../types/IScheduledSession";
function DashboardPage() { function DashboardPage() {
const { user } = useAuthStore(); const { user } = useAuthStore();
@@ -31,20 +24,19 @@ function DashboardPage() {
company, company,
setCompany, setCompany,
selectedBuild, selectedBuild,
setSelectedBuild,
builds, builds,
setBuilds, setBuilds,
managers, managers,
setManagers, setManagers,
schedules,
setSchedules,
selectedDay, selectedDay,
setSelectedDay, setSelectedDay,
} = useStore(); } = useStore();
const [, setDuration] = useState<number>(); // const [, setDuration] = useState<number>();
const [scheduledSessions, setScheduledSessions] = useState<any[]>(); const [scheduledSessions, setScheduledSessions] = useState<
const [, setGeneratedScheduledSessions] = useState<any[]>(); IScheduledSession[]
const [dateTimes, setDateTimes] = useState<Date[]>(); >([]);
// const [, setGeneratedScheduledSessions] = useState<any[]>();
// const [dateTimes, setDateTimes] = useState<Date[]>();
const [, setCurrentTime] = useState<string>(format(new Date(), "HH:mm")); const [, setCurrentTime] = useState<string>(format(new Date(), "HH:mm"));
const [, setIsLoadingScheduledSessions] = useState(true); const [, setIsLoadingScheduledSessions] = useState(true);
const scheduledSessionsRef = useRef<HTMLDivElement>(null); const scheduledSessionsRef = useRef<HTMLDivElement>(null);
@@ -61,78 +53,6 @@ function DashboardPage() {
setSelectedDay(new Date()); setSelectedDay(new Date());
} }
function findSessionsByTime(time: Date) {
if (!scheduledSessions || !scheduledSessions.length) return [];
const foundScheduledSessions = scheduledSessions?.filter(
(scheduledSession) => isEqual(new Date(scheduledSession.startAt), time)
);
return foundScheduledSessions;
}
function generateScheduledSessions() {
if (!selectedBuild) return;
const newGeneratedScheduledSession: any[] = [];
dateTimes?.forEach((time) => {
const foundSessionsByTime = findSessionsByTime(time);
const sessions: any[] = [];
if (foundSessionsByTime) {
sessions.push(...foundSessionsByTime);
}
for (
let index = 0;
index < selectedBuild.sessionLimit - foundSessionsByTime.length;
index++
) {
sessions.push({});
}
newGeneratedScheduledSession.push({
time,
sessions,
});
});
setGeneratedScheduledSessions(newGeneratedScheduledSession);
}
useEffect(() => {
if (!scheduledSessions || !selectedBuild || !selectedDay) return;
generateScheduledSessions();
}, [scheduledSessions, selectedBuild, selectedDay]);
useEffect(() => {
if (!schedules || !selectedDay || !selectedBuild) return;
console.log("schedules", schedules);
const foundSchedule = schedules.find(
(schedule) => schedule.buildId === selectedBuild.id
);
if (!foundSchedule) {
setDateTimes([]);
return;
}
const step = foundSchedule.sessionDuration + foundSchedule.sessionBreak; // 35
const newTimes = eachMinuteOfInterval(
{
start: parse(foundSchedule.startTime, "HH:mm", new Date(selectedDay)), // 11:00
end: parse(foundSchedule.endTime, "HH:mm", new Date(selectedDay)), // 20:00
},
{ step }
);
setDateTimes(newTimes);
setDuration(foundSchedule.sessionDuration);
}, [schedules, selectedDay, selectedBuild]);
async function getCompany() { async function getCompany() {
if (!user) { if (!user) {
console.log("No User", user); console.log("No User", user);
@@ -141,6 +61,7 @@ function DashboardPage() {
try { try {
const result: any = await api.get(`companies/${user.companyId}`).json(); const result: any = await api.get(`companies/${user.companyId}`).json();
setCompany(result); setCompany(result);
} catch (error) { } catch (error) {
if (error instanceof Error) { if (error instanceof Error) {
@@ -159,6 +80,7 @@ function DashboardPage() {
const result: any = await api const result: any = await api
.get(`companies/${user.companyId}/builds`) .get(`companies/${user.companyId}/builds`)
.json(); .json();
setBuilds(result); setBuilds(result);
} catch (error) { } catch (error) {
if (error instanceof Error) { if (error instanceof Error) {
@@ -196,22 +118,19 @@ function DashboardPage() {
return; return;
} }
if (!selectedBuild || !selectedBuild.id) {
console.log("No selectedBuild");
return;
}
if (useLoader) setIsLoadingScheduledSessions(true); if (useLoader) setIsLoadingScheduledSessions(true);
try { try {
const result: any = await api const result: any = await api
.get( .get(
`companies/${company.id}/builds/${ `companies/${
selectedBuild.id company.id
}/scheduled_sessions?date=${selectedDay.toISOString()}` }/scheduled_sessions?date=${selectedDay.toISOString()}`
) )
.json(); .json();
console.log(result);
setScheduledSessions(result); setScheduledSessions(result);
} catch (error) { } catch (error) {
if (error instanceof Error) { if (error instanceof Error) {
@@ -222,44 +141,6 @@ function DashboardPage() {
if (useLoader) setIsLoadingScheduledSessions(false); if (useLoader) setIsLoadingScheduledSessions(false);
} }
// async function updateScheduledSessionManager(
// scheduledSessionId: string,
// managerId: string | null
// ) {
// if (!company || !scheduledSessions) return;
// try {
// const result: any = await api
// .put(
// `companies/${company.id}/scheduled_sessions/${scheduledSessionId}`,
// {
// json: { userId: managerId },
// }
// )
// .json();
// setScheduledSessions(
// scheduledSessions.map((scheduledSession) =>
// scheduledSession.id === result.id ? result : scheduledSession
// )
// );
// } catch (error) {
// if (error instanceof Error) {
// console.log("Error: ", error.message);
// }
// }
// }
async function getSchedules() {
if (!company || !selectedBuild) return;
const result: any[] = await api
.get(`companies/${company.id}/builds/${selectedBuild.id}/schedules`)
.json();
setSchedules(result);
}
useEffect(() => { useEffect(() => {
getCompany(); getCompany();
@@ -274,16 +155,19 @@ function DashboardPage() {
useEffect(() => { useEffect(() => {
if (!company) return; if (!company) return;
getBuilds(); getBuilds();
}, [company]); }, [company]);
useEffect(() => { useEffect(() => {
if (!builds) return; if (!builds) return;
setSelectedBuild(builds[0]);
getManagers();
}, [builds]); }, [builds]);
useEffect(() => { useEffect(() => {
if (!managers || !selectedDay || !selectedBuild) return; if (!managers || !selectedDay || !company || !builds) return;
getScheduledSessions(true); getScheduledSessions(true);
const interval = setInterval(() => { const interval = setInterval(() => {
@@ -293,35 +177,17 @@ function DashboardPage() {
return () => { return () => {
clearInterval(interval); clearInterval(interval);
}; };
}, [managers, selectedDay, selectedBuild]); }, [managers, selectedDay, company, builds]);
useEffect(() => {
if (!company || !selectedBuild) return;
getSchedules();
getManagers();
}, [selectedBuild]);
useEffect(() => { useEffect(() => {
scheduledSessionsRef.current?.scrollTo({ top: 0, behavior: "smooth" }); scheduledSessionsRef.current?.scrollTo({ top: 0, behavior: "smooth" });
}, [selectedDay, selectedBuild]); }, [selectedDay]);
return ( return (
<div className="flex h-screen main"> <div className="flex h-screen main">
<div className="flex flex-col w-full left"> <div className="flex flex-col w-full left">
<div className="flex bg-[#F0F1F2]"> <div className="flex bg-[#F0F1F2]">
<Menu /> <Menu />
{builds?.map((build) => (
<TabButton
key={build.id}
handleClick={() => setSelectedBuild(build)}
active={
(selectedBuild && build.id === selectedBuild?.id) || false
}
>
{build.name}
</TabButton>
))}
</div> </div>
<div className="flex justify-between items-center px-4 py-2 h-12 border-r border-b border-[#DAE0E5]"> <div className="flex justify-between items-center px-4 py-2 h-12 border-r border-b border-[#DAE0E5]">
@@ -436,10 +302,10 @@ function DashboardPage() {
)} )}
</div> */} </div> */}
{selectedBuild?.sessionLimit && scheduledSessions && ( {company && scheduledSessions && (
<Schedule <Schedule
selectedDay={selectedDay} selectedDay={selectedDay}
slots={selectedBuild.sessionLimit} slots={company.sessionLimit}
events={scheduledSessions} events={scheduledSessions}
/> />
)} )}
+1
View File
@@ -1,6 +1,7 @@
interface ICompany { interface ICompany {
id: string; id: string;
name: string; name: string;
sessionLimit: number;
} }
export default ICompany; export default ICompany;
+1
View File
@@ -2,6 +2,7 @@ interface IScheduledSession {
id: string; id: string;
companyId: string; companyId: string;
buildId: string; buildId: string;
slot: number;
startAt: string; startAt: string;
endAt: string; endAt: string;
client?: unknown; client?: unknown;
+4 -4
View File
@@ -21,14 +21,14 @@ companySchema.virtual("users", {
foreignField: "companyId", foreignField: "companyId",
}); });
companySchema.virtual("scheduledSessions", { companySchema.virtual("builds", {
ref: "Scheduled_Session", ref: "Build",
localField: "_id", localField: "_id",
foreignField: "companyId", foreignField: "companyId",
}); });
companySchema.virtual("builds", { companySchema.virtual("scheduledSessions", {
ref: "Build", ref: "Scheduled_Session",
localField: "_id", localField: "_id",
foreignField: "companyId", foreignField: "companyId",
}); });
+1 -1
View File
@@ -5,7 +5,7 @@ const scheduledSessionSchema = new Schema(
companyId: { companyId: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: "Company", ref: "Company",
// required: true, required: true,
}, },
buildId: { buildId: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
+6 -12
View File
@@ -68,7 +68,7 @@ router.get("/:id/builds/:buildId/users", async (req, res) => {
res.json(users); res.json(users);
}); });
router.get("/:id/builds/:buildId/scheduled_sessions", async (req, res) => { router.get("/:id/scheduled_sessions", async (req, res) => {
if (req.params.id != res.locals.user.companyId) { if (req.params.id != res.locals.user.companyId) {
res.json({ error: "Access denied" }); res.json({ error: "Access denied" });
return; return;
@@ -82,22 +82,16 @@ router.get("/:id/builds/:buildId/scheduled_sessions", async (req, res) => {
const date = parseISO(req.query.date as string); const date = parseISO(req.query.date as string);
const company: any = await Company.findById(req.params.id).populate({ const company: any = await Company.findById(req.params.id).populate({
path: "builds", path: "scheduledSessions",
match: { match: {
_id: req.params.buildId, startAt: {
}, $gte: startOfDay(date),
populate: { $lte: endOfDay(date),
path: "scheduledSessions",
match: {
startAt: {
$gte: startOfDay(date),
$lte: endOfDay(date),
},
}, },
}, },
}); });
const { scheduledSessions } = company.builds[0]; const { scheduledSessions } = company;
res.json(scheduledSessions); res.json(scheduledSessions);
}); });
+4 -2
View File
@@ -75,9 +75,9 @@ router.get("/:buildId", async (req, res) => {
}); });
router.post("/", async (req, res) => { router.post("/", async (req, res) => {
const { buildId, slot, startAt, client, duration } = req.body; const { companyId, buildId, slot, startAt, client, duration } = req.body;
if (!buildId || !startAt || !slot) { if (!companyId || !buildId || !startAt || !slot) {
return res.json({ return res.json({
status: "error", status: "error",
message: "Parameters `buildId`, `startAt`, `slot` are required!", // Параметры `buildId`, `startAt`, `slot` обязательны! message: "Parameters `buildId`, `startAt`, `slot` are required!", // Параметры `buildId`, `startAt`, `slot` обязательны!
@@ -104,6 +104,7 @@ router.post("/", async (req, res) => {
if (duration) { if (duration) {
const scheduledSessions = await ScheduledSession.find({ const scheduledSessions = await ScheduledSession.find({
companyId,
buildId, buildId,
slot, slot,
startAt: { startAt: {
@@ -141,6 +142,7 @@ router.post("/", async (req, res) => {
} }
const scheduledSession = await ScheduledSession.create({ const scheduledSession = await ScheduledSession.create({
companyId,
buildId, buildId,
slot, slot,
startAt: startAtISO, startAt: startAtISO,