This commit is contained in:
2023-10-18 18:16:29 +05:00
parent c5aa7bd524
commit 45e5b95dd9
6 changed files with 299 additions and 62 deletions
+4 -2
View File
@@ -2,7 +2,7 @@ import SpinnerIcon from "./icons/SpinnerIcon";
interface ButtonProps {
type?: "button" | "reset" | "submit";
color?: "primary" | "secondary";
color?: "primary" | "secondary" | "tertiary";
size?: "small" | "medium";
disabled?: boolean;
loading?: boolean;
@@ -33,7 +33,9 @@ function Button({
className={`relative outline-none rounded-lg transition-colors font-semibold flex justify-center items-center gap-1 active:bg-[#49A1F5] disabled:bg-[#F2F2F2] disabled:text-[#CCCCCC] ${
(color === "primary" && "bg-[#49A1F5] text-white hover:bg-[#4190DB]") ||
(color === "secondary" &&
"bg-[#F0F1F2] text-[#77828C] active:text-white")
"bg-[#F0F1F2] text-[#77828C] hover:bg-[#E6ECF2] active:text-white") ||
(color === "tertiary" &&
"text-[#77828C] hover:bg-[#E6ECF2] active:text-white")
} ${
(size === "small" &&
`h-8 ${
+1 -2
View File
@@ -26,8 +26,7 @@ function SelectUser({
{(state) => (
<div
ref={selectUserRef}
className={`bg-white w-[280px] max-h-[126px] overflow-auto rounded-lg py-2 flex flex-col gap-1 transition-opacity ${state}`}
style={{ boxShadow: "0px 1px 4px 0px rgba(0, 0, 0, 0.16)" }}
className={`bg-white w-[280px] max-h-[126px] overflow-auto rounded-lg py-2 flex flex-col gap-1 shadow-md transition-opacity ${state}`}
>
{managers.map((manager) => (
<button
+93 -56
View File
@@ -15,64 +15,101 @@ import {
addWeeks,
differenceInDays,
eachMinuteOfInterval,
endOfDay,
format,
parse,
startOfDay,
} from "date-fns";
import api from "../../utils/api";
function CreateSchedule() {
interface CreateScheduleProps {
companyId: string;
buildId: string;
handleCreate: () => void;
}
function CreateSchedule({
companyId,
buildId,
handleCreate,
}: CreateScheduleProps) {
const setModal = useModalStore((state) => state.setModal);
const [selectedScheduleDate, setSelectedScheduleDate] = useState<Date | null>(
new Date()
);
const [selectedScheduleDuration, setSelectedScheduleDuration] =
useState<number>(3);
const [scheduleStartDate, setScheduleStartDate] = useState<Date>();
const [scheduleEndDate, setScheduleEndDate] = useState<Date>();
const [selectedSessionDuration, setSelectedSessionDuration] =
useState<number>(30);
const [selectedSessionsBreak, setSelectedSessionsBreak] = useState<number>(5);
const [selectedWorkTimeStart, setSelectedWorkTimeStart] =
useState<string>("10:00");
const [selectedWorkTimeEnd, setSelectedWorkTimeEnd] =
useState<string>("20:00");
const [sessionsCount, setSessionsCount] = useState<number>();
const [date, setDate] = useState<Date | null>(new Date());
const [scheduleDuration, setScheduleDuration] = useState<number>(3);
const [startDate, setStartDate] = useState<Date>();
const [endDate, setEndDate] = useState<Date>();
const [sessionDuration, setSessionDuration] = useState<number>(30);
const [sessionBreak, setSessionBreak] = useState<number>(5);
const [startTime, setStartTime] = useState<string>("10:00");
const [endTime, setEndTime] = useState<string>("20:00");
const [sessionCount, setSessionCount] = useState<number>();
useEffect(() => {
if (!selectedScheduleDate || !selectedScheduleDuration) return;
if (!date || !scheduleDuration) return;
setScheduleStartDate(selectedScheduleDate);
setScheduleEndDate(
selectedScheduleDuration !== 3
? addWeeks(selectedScheduleDate, selectedScheduleDuration)
: addMonths(selectedScheduleDate, 1)
setStartDate(startOfDay(date));
setEndDate(
endOfDay(
scheduleDuration !== 3
? addWeeks(date, scheduleDuration)
: addMonths(date, 1)
)
);
}, [selectedScheduleDate, selectedScheduleDuration]);
}, [date, scheduleDuration]);
function calculateSessionCount() {
if (!scheduleStartDate || !scheduleEndDate) return;
if (!startDate || !endDate) return;
const sessionsPerDay = eachMinuteOfInterval(
{
start: parse(selectedWorkTimeStart, "HH:mm", new Date()),
end: parse(selectedWorkTimeEnd, "HH:mm", new Date()),
start: parse(startTime, "HH:mm", new Date()),
end: parse(endTime, "HH:mm", new Date()),
},
{ step: selectedSessionDuration + selectedSessionsBreak }
{ step: sessionDuration + sessionBreak }
).length;
const days = differenceInDays(scheduleEndDate, scheduleStartDate);
const days = differenceInDays(endDate, startDate);
setSessionsCount(days * sessionsPerDay);
setSessionCount(days * sessionsPerDay);
}
function handleChangedate(value: Date) {
if (differenceInDays(value, new Date()) >= 0) {
setDate(value);
} else {
setDate(new Date());
}
}
function changeendTime(value: string) {
if (value.split(":")[0] > startTime.split(":")[0]) {
setEndTime(value);
}
}
async function createSchedule() {
await api.post(`companies/${companyId}/builds/${buildId}/schedules`, {
json: {
startDate,
endDate,
startTime,
endTime,
sessionDuration,
sessionBreak,
sessionCount,
},
});
}
async function handleClickCreateSchedule() {
await createSchedule();
handleCreate();
setModal(null);
}
useEffect(() => {
calculateSessionCount();
}, [
scheduleStartDate,
scheduleEndDate,
selectedWorkTimeEnd,
selectedSessionDuration,
selectedSessionsBreak,
]);
}, [startDate, endDate, endTime, sessionDuration, sessionBreak]);
return (
<div className="bg-white text-sm relative z-10 rounded-lg w-[896px] shadow-md">
@@ -101,8 +138,8 @@ function CreateSchedule() {
<DatePicker
placeholderText="Выберите дату"
selected={selectedScheduleDate}
onChange={(value) => setSelectedScheduleDate(value)}
selected={date}
onChange={handleChangedate}
locale={ru}
dateFormat="dd.MM.yyyy"
className="mt-1 font-normal px-3 py-2.5 rounded-lg border border-[#DAE0E5] text-black w-full outline-none focus:border-[#49A1F5] transition-colors"
@@ -112,13 +149,13 @@ function CreateSchedule() {
<div className="flex flex-col gap-1">
<Label value="Продолжительность" />
<Select
defaultValue={selectedScheduleDuration}
defaultValue={scheduleDuration}
options={[
{ value: 1, text: "1 неделя" },
{ value: 2, text: "2 недели" },
{ value: 3, text: "1 месяц" },
]}
handleChange={(value) => setSelectedScheduleDuration(value)}
handleChange={(value) => setScheduleDuration(value)}
/>
</div>
</div>
@@ -136,8 +173,8 @@ function CreateSchedule() {
<Label value="Начало" />
<Input
type="time"
defaultValue={selectedWorkTimeStart}
handleChange={(value) => setSelectedWorkTimeStart(value)}
defaultValue={startTime}
handleChange={(value) => setStartTime(value)}
className="w-[137px]"
/>
</div>
@@ -146,8 +183,8 @@ function CreateSchedule() {
<Label value="Конец" />
<Input
type="time"
defaultValue={selectedWorkTimeEnd}
handleChange={(value) => setSelectedWorkTimeEnd(value)}
defaultValue={endTime}
handleChange={changeendTime}
className="w-[137px]"
/>
</div>
@@ -166,7 +203,7 @@ function CreateSchedule() {
<div className="w-[296px]">
<Select
defaultValue={selectedSessionDuration}
defaultValue={sessionDuration}
options={[
{ value: 5, text: "5 мин." },
{ value: 10, text: "10 мин." },
@@ -181,7 +218,7 @@ function CreateSchedule() {
{ value: 55, text: "55 мин." },
{ value: 60, text: "60 мин." },
]}
handleChange={(value) => setSelectedSessionDuration(value)}
handleChange={(value) => setSessionDuration(value)}
/>
</div>
</div>
@@ -212,7 +249,7 @@ function CreateSchedule() {
{ value: 55, text: "55 мин." },
{ value: 60, text: "60 мин." },
]}
handleChange={(value) => setSelectedSessionsBreak(value)}
handleChange={(value) => setSessionBreak(value)}
/>
</div>
</div>
@@ -225,34 +262,34 @@ function CreateSchedule() {
<div className="p-4 flex flex-col gap-4">
<p className="text-sm font-semibold">Предварительный просмотр</p>
<div className="flex flex-col gap-3 text-xs">
{scheduleStartDate && scheduleEndDate && (
{startDate && endDate && (
<p className="font-semibold flex gap-1">
<span>{format(scheduleStartDate, "dd.MM.yyyy")}</span>
<span>{format(startDate, "dd.MM.yyyy")}</span>
<span>-</span>
<span>{format(scheduleEndDate, "dd.MM.yyyy")}</span>
<span>{format(endDate, "dd.MM.yyyy")}</span>
</p>
)}
<div className="flex flex-col gap-2">
<div className="grid grid-cols-3">
<p className="col-span-2 text-[#77828C]">
Количество сеансов
Общее кол-во сеансов
</p>
<p>{sessionsCount}</p>
<p>{sessionCount}</p>
</div>
<div className="grid grid-cols-3">
<p className="col-span-2 text-[#77828C]">
Длительность сеанса
</p>
<p>{selectedSessionDuration} мин.</p>
<p>{sessionDuration} мин.</p>
</div>
<div className="grid grid-cols-3">
<p className="col-span-2 text-[#77828C]">Между сеансами</p>
<p>{selectedSessionsBreak} мин.</p>
<p>{sessionBreak} мин.</p>
</div>
<div className="grid grid-cols-3">
<p className="col-span-2 text-[#77828C]">Время работы</p>
<p>
{selectedWorkTimeStart} - {selectedWorkTimeEnd}
{startTime} - {endTime}
</p>
</div>
</div>
@@ -262,7 +299,7 @@ function CreateSchedule() {
</div>
<div className="px-6 py-3 flex gap-2">
<Button>Сохранить</Button>
<Button handleClick={handleClickCreateSchedule}>Создать</Button>
<Button color="secondary" handleClick={() => setModal(null)}>
Отмена
</Button>
+118 -2
View File
@@ -15,6 +15,9 @@ import {
parse,
addDays,
subDays,
parseISO,
isAfter,
isBefore,
} from "date-fns";
import Button from "../components/Button";
import { ru } from "date-fns/locale";
@@ -23,6 +26,7 @@ import SpinnerIcon from "../components/icons/SpinnerIcon";
import useModalStore from "../stores/useModalStore";
import ModalContainer from "../components/ModalContainer";
import CreateSchedule from "../components/modals/CreateSchedule";
import MoreIcon from "../components/icons/MoreIcon";
function DashboardPage() {
const [user, setAccessToken] = useAuthStore((state) => [
@@ -33,6 +37,7 @@ function DashboardPage() {
const [managers, setManagers] = useState<any[]>();
const [builds, setBuilds] = useState<any[]>();
const [selectedBuild, setSelectedBuild] = useState<{ [key: string]: any }>();
const [schedules, setSchedules] = useState<any[]>();
const [scheduledSessions, setScheduledSessions] = useState<any[]>();
const [generatedScheduledSessions, setGeneratedScheduledSessions] =
useState<any[][]>();
@@ -70,6 +75,21 @@ function DashboardPage() {
setSelectedDate(new Date());
}
useEffect(() => {
if (!schedules?.length) return;
schedules.map((schedule) => {
if (
isAfter(selectedDate, parseISO(schedule.startDate)) &&
isBefore(selectedDate, parseISO(schedule.endDate))
) {
console.log("FIND");
} else {
console.log("Not find");
}
});
}, [schedules, scheduledSessions]);
async function getCompany() {
if (!user) {
console.log("No User", user);
@@ -214,6 +234,16 @@ function DashboardPage() {
}
}
async function getSchedules() {
if (!company || !selectedBuild) return;
const result: any[] = await api
.get(`companies/${company.id}/builds/${selectedBuild.id}/schedules`)
.json();
setSchedules(result);
}
useEffect(() => {
getCompany();
@@ -250,6 +280,12 @@ function DashboardPage() {
};
}, [managers, selectedDate, selectedBuild]);
useEffect(() => {
if (!company || !selectedBuild) return;
getSchedules();
}, [selectedBuild]);
useEffect(() => {
if (!scheduledSessions) return;
generateScheduledSessions();
@@ -429,12 +465,92 @@ function DashboardPage() {
<div className="p-4 flex flex-col gap-4">
<p className="text-sm font-semibold">Расписание</p>
<div className=""></div>
{schedules?.map((schedule) => (
<div key={schedule.id} className="flex flex-col gap-3 text-xs">
<p className="font-semibold flex gap-1">
<span>
{format(parseISO(schedule.startDate), "dd.MM.yyyy")}
</span>
<span>-</span>
<span>
{format(parseISO(schedule.endDate), "dd.MM.yyyy")}
</span>
</p>
<div className="flex flex-col gap-2">
<div className="grid grid-cols-3">
<p className="col-span-2 text-[#77828C]">
Общее кол-во сеансов
</p>
<p>{schedule.sessionCount}</p>
</div>
<div className="grid grid-cols-3">
<p className="col-span-2 text-[#77828C]">
Длительность сеанса
</p>
<p>{schedule.sessionDuration} мин.</p>
</div>
<div className="grid grid-cols-3">
<p className="col-span-2 text-[#77828C]">Между сеансами</p>
<p>{schedule.sessionBreak} мин.</p>
</div>
<div className="grid grid-cols-3">
<p className="col-span-2 text-[#77828C]">Время работы</p>
<p>
{schedule.startTime} - {schedule.endTime}
</p>
</div>
</div>
</div>
))}
<Button
color="secondary"
className="w-full"
handleClick={() => setModal(<CreateSchedule />)}
handleClick={() =>
setModal(
<CreateSchedule
companyId={company?.id}
buildId={selectedBuild?.id}
handleCreate={getSchedules}
/>
)
}
>
Добавить
</Button>
</div>
<div className="p-4 flex flex-col gap-4">
<p className="text-sm font-semibold">Менеджеры</p>
<div className="flex flex-col gap-2">
{managers?.map((manager) => (
<div key={manager.id} className="flex justify-between">
<div className="flex items-center gap-2">
<img
src={manager.avatar}
alt=""
className="w-8 h-8 rounded-full"
/>
<p className="text-sm leading-[140%]">{manager.name}</p>
</div>
<Button
disabled
color="tertiary"
onlyIcon
icon={<MoreIcon />}
handleClick={() => {}}
/>
</div>
))}
</div>
<Button
disabled
color="secondary"
className="w-full"
handleClick={() => {}}
>
Добавить
</Button>
+53
View File
@@ -0,0 +1,53 @@
import { model, Schema } from "mongoose";
const scheduleSchema = new Schema(
{
companyId: {
type: Schema.Types.ObjectId,
ref: "Company",
required: true,
},
buildId: {
type: Schema.Types.ObjectId,
ref: "Build",
required: true,
},
startDate: {
type: Date,
required: true,
},
endDate: {
type: Date,
required: true,
},
startTime: {
type: String,
required: true,
},
endTime: {
type: String,
required: true,
},
sessionDuration: {
type: Number,
required: true,
},
sessionBreak: {
type: Number,
required: true,
},
sessionCount: {
type: Number,
required: true,
},
},
{
timestamps: true,
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
);
const Schedule = model("Schedule", scheduleSchema);
export default Schedule;
+30
View File
@@ -2,6 +2,7 @@ import { Router } from "express";
import Company from "../models/Company.js";
import { parseISO, startOfDay, endOfDay } from "date-fns";
import ScheduledSession from "../models/ScheduledSession.js";
import Schedule from "../models/Schedule.js";
const companiesRouter = Router();
@@ -201,4 +202,33 @@ companiesRouter.get(
}
);
companiesRouter.get("/:id/builds/:buildId/schedules", async (req, res) => {
if (req.params.id != res.locals.user.companyId) {
res.json({ error: "Access denied!" });
return;
}
const schedules = await Schedule.find({
companyId: req.params.id,
buildId: req.params.buildId,
});
res.json(schedules);
});
companiesRouter.post("/:id/builds/:buildId/schedules", async (req, res) => {
if (req.params.id != res.locals.user.companyId) {
res.json({ error: "Access denied!" });
return;
}
const schedule = await Schedule.create({
companyId: req.params.id,
buildId: req.params.buildId,
...req.body,
});
res.json(schedule);
});
export default companiesRouter;