upd
This commit is contained in:
@@ -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 ${
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user