This commit is contained in:
2023-10-17 18:58:51 +05:00
parent 4c83933741
commit c5aa7bd524
15 changed files with 589 additions and 41 deletions
+2
View File
@@ -13,6 +13,7 @@
"date-fns": "^2.30.0",
"ky": "^1.0.1",
"react": "^18.2.0",
"react-datepicker": "^4.20.0",
"react-dom": "^18.2.0",
"react-google-recaptcha": "^3.1.0",
"react-router-dom": "^6.15.0",
@@ -21,6 +22,7 @@
},
"devDependencies": {
"@types/react": "^18.2.15",
"@types/react-datepicker": "^4.19.0",
"@types/react-dom": "^18.2.7",
"@types/react-google-recaptcha": "^2.1.6",
"@types/react-transition-group": "^4.4.7",
+10 -2
View File
@@ -6,6 +6,7 @@ interface ButtonProps {
size?: "small" | "medium";
disabled?: boolean;
loading?: boolean;
onlyIcon?: boolean;
icon?: React.ReactNode;
children?: React.ReactNode;
className?: string;
@@ -18,6 +19,7 @@ function Button({
size = "small",
disabled,
loading = false,
onlyIcon,
icon,
children,
className,
@@ -33,8 +35,14 @@ function Button({
(color === "secondary" &&
"bg-[#F0F1F2] text-[#77828C] active:text-white")
} ${
(size === "small" && `h-[32px] px-3 py-2.5 text-xs`) ||
(size === "medium" && "h-[40px] px-6 py-3 text-sm")
(size === "small" &&
`h-8 ${
onlyIcon ? "p-1" : icon ? "pl-2 pr-4 py-1" : "px-3 py-2.5"
} text-xs`) ||
(size === "medium" &&
`h-10 ${
onlyIcon ? "p-2" : icon ? "pl-4 pr-6 py-2" : "px-6 py-3"
} text-sm`)
} ${className}`}
>
{loading ? <SpinnerIcon /> : icon} {children}
+6 -6
View File
@@ -44,8 +44,8 @@ function Card({
)
.json();
const filteredManagers = managers.filter(
(manager) => !result.includes(manager.id)
const filteredManagers = managers.filter((manager) =>
result.includes(manager.id)
);
setAvailableManagers(filteredManagers);
@@ -62,10 +62,10 @@ function Card({
<div className="flex justify-between">
<p className="text-sm font-semibold">{client.name}</p>
{manager ? (
<div className="bg-[#DAF2E4] rounded-full px-2 h-[20px] flex items-center gap-1">
<div className="w-1 h-1 rounded-full bg-[#27AE60]"></div>
<p className="text-[10px] font-semibold text-[#27AE60] pt-0.5">
Сеанс начат
<div className="bg-[#E6F2FE] rounded-full px-2 h-[20px] flex items-center gap-1">
<div className="w-1 h-1 rounded-full bg-[#49A1F5]"></div>
<p className="text-[10px] font-semibold text-[#49A1F5] pt-0.5">
Готов
</p>
</div>
) : (
+16 -6
View File
@@ -1,11 +1,16 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { useEffect, useState } from "react";
interface InputProps {
type?: "text" | "email" | "password";
type?: "text" | "email" | "password" | "time";
placeholder?: string;
autoFocus?: boolean;
required?: boolean;
readOnly?: boolean;
defaultValue?: string;
className?: string;
handleChange?: (value: string) => void;
handleFocus?: () => void;
}
function Input({
@@ -13,15 +18,18 @@ function Input({
placeholder,
autoFocus,
required,
readOnly,
defaultValue,
className,
handleChange,
handleFocus,
}: InputProps) {
const [value, setValue] = useState<string>("");
const [value, setValue] = useState<string | undefined>(defaultValue);
useEffect(() => {
if (handleChange) {
if (value && handleChange) {
handleChange(value);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [value]);
return (
@@ -30,9 +38,11 @@ function Input({
placeholder={placeholder}
autoFocus={autoFocus}
required={required}
value={value}
readOnly={readOnly}
value={defaultValue}
onChange={(e) => setValue(e.target.value)}
className="px-3 py-2.5 outline-none rounded-lg border border-[#DAE0E5]"
onFocus={handleFocus}
className={`px-3 py-2.5 outline-none rounded-lg border border-[#DAE0E5] focus:border-[#49A1F5] transition-colors ${className}`}
/>
);
}
+31
View File
@@ -0,0 +1,31 @@
import { Transition } from "react-transition-group";
import useModalStore from "../stores/useModalStore";
function ModalContainer() {
const [modal, setModal] = useModalStore((state) => [
state.modal,
state.setModal,
]);
return (
<Transition
in={modal ? true : false}
timeout={150}
mountOnEnter
unmountOnExit
>
{(state) => (
<div
onClick={() => setModal(null)}
className={`min-h-screen p-8 absolute top-0 left-0 w-full flex justify-center items-center bg-black bg-opacity-30 overflow-auto cursor-pointer transition-opacity ${state}`}
>
<div onClick={(e) => e.stopPropagation()} className="cursor-default">
{modal}
</div>
</div>
)}
</Transition>
);
}
export default ModalContainer;
+73
View File
@@ -0,0 +1,73 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { useEffect, useState } from "react";
import Input from "./Input";
import CheckIcon from "./icons/CheckIcon";
import { Transition } from "react-transition-group";
import useOutsideClick from "../hooks/useOutsideClick";
import ChevronDown from "./icons/ChevronDown";
/* eslint-disable @typescript-eslint/no-explicit-any */
interface SelectProps {
defaultValue?: number;
options?: any[];
handleChange: (value: number) => void;
}
function Select({ defaultValue, options, handleChange }: SelectProps) {
const [value, setValue] = useState<number | undefined>(defaultValue);
const [isShow, setIsShow] = useState<boolean>(false);
const selectRef = useOutsideClick(() => setIsShow(false));
function handleClick(option: { [key: string]: any }) {
setValue(option.value);
setIsShow(false);
}
useEffect(() => {
if (!value) return;
handleChange(value);
}, [value]);
return (
<div ref={selectRef} className="relative">
<Input
readOnly
handleFocus={() => setIsShow(true)}
defaultValue={options?.find((option) => option.value === value).text}
className="w-full cursor-pointer"
/>
<button
onClick={() => setIsShow(true)}
className="absolute right-0 top-0 px-3 py-2 text-[#77828C]"
>
<ChevronDown />
</button>
<Transition in={isShow} timeout={150} mountOnEnter unmountOnExit>
{(state) => (
<div
className={`absolute z-10 mt-2 max-h-[126px] overflow-auto rounded-lg flex flex-col gap-1 py-2 bg-white w-full shadow-md transition-opacity ${state}`}
>
{options?.map((option, index) => (
<button
key={index}
onClick={() => handleClick(option)}
value={option.value}
className="h-8 px-4 py-2 leading-[130%] text-left hover:bg-[#E6ECF2] transition-colors flex justify-between items-center"
>
<p>{option.text}</p>
{value === option.value && (
<span className="text-[#49A1F5]">
<CheckIcon />
</span>
)}
</button>
))}
</div>
)}
</Transition>
</div>
);
}
export default Select;
+21
View File
@@ -0,0 +1,21 @@
function CloseIcon() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 11.9999L17.6569 6.34331M12 11.9999L6.34312 6.34302M12 11.9999L17.6568 17.6567M12 11.9999L6.34302 17.6568"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
export default CloseIcon;
@@ -0,0 +1,274 @@
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable no-irregular-whitespace */
import { ru } from "date-fns/locale";
import Button from "../Button";
import CloseIcon from "../icons/CloseIcon";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import Label from "../Label";
import { useEffect, useState } from "react";
import Select from "../Select";
import useModalStore from "../../stores/useModalStore";
import Input from "../Input";
import {
addMonths,
addWeeks,
differenceInDays,
eachMinuteOfInterval,
format,
parse,
} from "date-fns";
function CreateSchedule() {
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>();
useEffect(() => {
if (!selectedScheduleDate || !selectedScheduleDuration) return;
setScheduleStartDate(selectedScheduleDate);
setScheduleEndDate(
selectedScheduleDuration !== 3
? addWeeks(selectedScheduleDate, selectedScheduleDuration)
: addMonths(selectedScheduleDate, 1)
);
}, [selectedScheduleDate, selectedScheduleDuration]);
function calculateSessionCount() {
if (!scheduleStartDate || !scheduleEndDate) return;
const sessionsPerDay = eachMinuteOfInterval(
{
start: parse(selectedWorkTimeStart, "HH:mm", new Date()),
end: parse(selectedWorkTimeEnd, "HH:mm", new Date()),
},
{ step: selectedSessionDuration + selectedSessionsBreak }
).length;
const days = differenceInDays(scheduleEndDate, scheduleStartDate);
setSessionsCount(days * sessionsPerDay);
}
useEffect(() => {
calculateSessionCount();
}, [
scheduleStartDate,
scheduleEndDate,
selectedWorkTimeEnd,
selectedSessionDuration,
selectedSessionsBreak,
]);
return (
<div className="bg-white text-sm relative z-10 rounded-lg w-[896px] shadow-md">
<div className="border-b border-[#DAE0E5] pl-2 pr-3 h-12 flex justify-between items-center">
<p className="p-4 font-semibold leading-[115%]">Создание расписания</p>
<Button
color="secondary"
onlyIcon
icon={<CloseIcon />}
className="bg-transparent"
handleClick={() => setModal(null)}
/>
</div>
<div className="border-b border-[#DAE0E5] flex">
<div className="w-full px-6 border-r border-[#DAE0E5]">
<div className="py-8 border-b border-[#DAE0E5] flex justify-between">
<div className="">
<p className="font-semibold">Срок действия расписания</p>
<div className=""></div>
</div>
<div className="w-[296px] flex flex-col gap-3">
<div className="flex flex-col">
<Label value="Начало" />
<DatePicker
placeholderText="Выберите дату"
selected={selectedScheduleDate}
onChange={(value) => setSelectedScheduleDate(value)}
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"
/>
</div>
<div className="flex flex-col gap-1">
<Label value="Продолжительность" />
<Select
defaultValue={selectedScheduleDuration}
options={[
{ value: 1, text: "1 неделя" },
{ value: 2, text: "2 недели" },
{ value: 3, text: "1 месяц" },
]}
handleChange={(value) => setSelectedScheduleDuration(value)}
/>
</div>
</div>
</div>
<div className="py-8 border-b border-[#DAE0E5] flex justify-between">
<div className="">
<p className="font-semibold">Рабочее время</p>
<div className=""></div>
</div>
<div className="w-[296px]">
<div className="flex items-center gap-2">
<div className="flex flex-col gap-1">
<Label value="Начало" />
<Input
type="time"
defaultValue={selectedWorkTimeStart}
handleChange={(value) => setSelectedWorkTimeStart(value)}
className="w-[137px]"
/>
</div>
<p className="mt-4 text-[#77828C]">-</p>
<div className="flex flex-col gap-1">
<Label value="Конец" />
<Input
type="time"
defaultValue={selectedWorkTimeEnd}
handleChange={(value) => setSelectedWorkTimeEnd(value)}
className="w-[137px]"
/>
</div>
</div>
</div>
</div>
<div className="py-8 border-b border-[#DAE0E5] flex flex-col gap-8">
<div className="flex justify-between">
<div className="flex flex-col gap-2">
<p className="font-semibold">Продолжительность сеанса</p>
<p className="text-[#77828C] text-xs w-[240px]">
Влияет на количество ежедневно проводимых сессий
</p>
</div>
<div className="w-[296px]">
<Select
defaultValue={selectedSessionDuration}
options={[
{ value: 5, text: "5 мин." },
{ value: 10, text: "10 мин." },
{ value: 15, text: "15 мин." },
{ value: 20, text: "20 мин." },
{ value: 25, text: "25 мин." },
{ value: 30, text: "30 мин." },
{ value: 35, text: "35 мин." },
{ value: 40, text: "40 мин." },
{ value: 45, text: "45 мин." },
{ value: 50, text: "50 мин." },
{ value: 55, text: "55 мин." },
{ value: 60, text: "60 мин." },
]}
handleChange={(value) => setSelectedSessionDuration(value)}
/>
</div>
</div>
<div className="flex justify-between">
<div className="flex flex-col gap-2">
<p className="font-semibold">Перерыв между сеансами</p>
<p className="text-[#77828C] text-xs w-[240px]">
Нужен, чтобы менеджеры успевали завершать один сеанс и
переходить к следующему
</p>
</div>
<div className="w-[296px]">
<Select
defaultValue={5}
options={[
{ value: 5, text: "5 мин." },
{ value: 10, text: "10 мин." },
{ value: 15, text: "15 мин." },
{ value: 20, text: "20 мин." },
{ value: 25, text: "25 мин." },
{ value: 30, text: "30 мин." },
{ value: 35, text: "35 мин." },
{ value: 40, text: "40 мин." },
{ value: 45, text: "45 мин." },
{ value: 50, text: "50 мин." },
{ value: 55, text: "55 мин." },
{ value: 60, text: "60 мин." },
]}
handleChange={(value) => setSelectedSessionsBreak(value)}
/>
</div>
</div>
</div>
</div>
<div className="min-w-[296px]">
{/* <div className="p-4 border-b border-[#DAE0E5] ">
<p className="text-sm font-semibold">Статистика</p>
</div> */}
<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 && (
<p className="font-semibold flex gap-1">
<span>{format(scheduleStartDate, "dd.MM.yyyy")}</span>
<span>-</span>
<span>{format(scheduleEndDate, "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>
</div>
<div className="grid grid-cols-3">
<p className="col-span-2 text-[#77828C]">
Длительность сеанса
</p>
<p>{selectedSessionDuration} мин.</p>
</div>
<div className="grid grid-cols-3">
<p className="col-span-2 text-[#77828C]">Между сеансами</p>
<p>{selectedSessionsBreak} мин.</p>
</div>
<div className="grid grid-cols-3">
<p className="col-span-2 text-[#77828C]">Время работы</p>
<p>
{selectedWorkTimeStart} - {selectedWorkTimeEnd}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="px-6 py-3 flex gap-2">
<Button>Сохранить</Button>
<Button color="secondary" handleClick={() => setModal(null)}>
Отмена
</Button>
</div>
</div>
);
}
export default CreateSchedule;
+18 -2
View File
@@ -1,4 +1,3 @@
/* @import url("https://fonts.googleapis.com/css2?family=Manrope:wght@400;600&display=swap"); */
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap");
@tailwind base;
@@ -6,11 +5,12 @@
@tailwind utilities;
body {
/* font-family: "Manrope", sans-serif; */
font-family: "Inter", sans-serif;
color: #111c26;
}
/* Scrollbar */
*::-webkit-scrollbar {
width: 8px;
}
@@ -25,6 +25,8 @@ body {
border-width: 2px;
}
/* Transition */
.entering {
opacity: 1;
}
@@ -40,3 +42,17 @@ body {
.exited {
opacity: 0;
}
/* DatePicker */
.react-datepicker {
font-family: inherit !important;
}
.react-datepicker__day--selected {
background-color: #49a1f5 !important;
}
.react-datepicker__day--selected:hover {
background-color: #4190db !important;
}
+25 -2
View File
@@ -20,6 +20,9 @@ import Button from "../components/Button";
import { ru } from "date-fns/locale";
import { Transition } from "react-transition-group";
import SpinnerIcon from "../components/icons/SpinnerIcon";
import useModalStore from "../stores/useModalStore";
import ModalContainer from "../components/ModalContainer";
import CreateSchedule from "../components/modals/CreateSchedule";
function DashboardPage() {
const [user, setAccessToken] = useAuthStore((state) => [
@@ -49,6 +52,7 @@ function DashboardPage() {
const [isLoadingScheduledSessions, setIsLoadingScheduledSessions] =
useState(true);
const scheduledSessionsRef = useRef<HTMLDivElement>(null);
const setModal = useModalStore((state) => state.setModal);
// const [selectedDate, setSelectedDate] = useState(
// format(new Date(), "d MMMM, yyyy", { locale: ru })
@@ -288,11 +292,13 @@ function DashboardPage() {
handleClick={selectPrevDay}
icon={<ChevronLeftIcon />}
color="secondary"
onlyIcon
/>
<Button
handleClick={selectNextDay}
icon={<ChevronRightIcon />}
color="secondary"
onlyIcon
/>
</div>
@@ -375,7 +381,6 @@ function DashboardPage() {
}}
manager={selectedManager}
managers={managers}
// managers={managers}
handleSelect={(scheduledSessionId, managerId) =>
updateScheduledSessionManager(
scheduledSessionId,
@@ -417,10 +422,28 @@ function DashboardPage() {
</button>
</div>
<div className="overflow-y-auto overflow-x-hidden">
<p className="p-4">...</p>
<div className="p-4">
<p className="text-sm font-semibold opacity-50">Календарь</p>
</div>
<div className="p-4 flex flex-col gap-4">
<p className="text-sm font-semibold">Расписание</p>
<div className=""></div>
<Button
color="secondary"
className="w-full"
handleClick={() => setModal(<CreateSchedule />)}
>
Добавить
</Button>
</div>
</div>
</div>
<ModalContainer />
</div>
);
}
+2 -2
View File
@@ -10,12 +10,12 @@ function RegistrationPage() {
<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 className="h-[56px] px-6 py-4 rounded-lg border border-[#DAE0E5] hover:border-[#49A1F5] text-[#77828C] hover:text-[#111C26] text-left w-full transition-colors">
Я представитель компании
</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 className="h-[56px] px-6 py-4 rounded-lg border border-[#DAE0E5] hover:border-[#49A1F5] text-[#77828C] hover:text-[#111C26] text-left w-full transition-colors">
Я менеджер отдела продаж
</button>
</Link>
+13
View File
@@ -0,0 +1,13 @@
import { create } from "zustand";
interface ModalState {
modal: React.ReactNode | null;
setModal: (modal: React.ReactNode) => void;
}
const useModalStore = create<ModalState>((set) => ({
modal: null,
setModal: (modal) => set({ modal }),
}));
export default useModalStore;
+60 -3
View File
@@ -233,6 +233,11 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@popperjs/core@^2.11.8", "@popperjs/core@^2.9.2":
version "2.11.8"
resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz"
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
"@remix-run/router@1.8.0":
version "1.8.0"
resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.8.0.tgz"
@@ -321,6 +326,16 @@
resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz"
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
"@types/react-datepicker@^4.19.0":
version "4.19.0"
resolved "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-4.19.0.tgz"
integrity sha512-461OQou2oHJIfqeTPY+8lBn/V4Z7usmJhcGDWO/R8wQYm+H5/Cqsrwto89J2RTbhWdiynmvh78Beh8txN5y0mA==
dependencies:
"@popperjs/core" "^2.9.2"
"@types/react" "*"
date-fns "^2.0.1"
react-popper "^2.2.5"
"@types/react-dom@^18.2.7":
version "18.2.7"
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz"
@@ -598,6 +613,11 @@ chokidar@^3.5.3:
optionalDependencies:
fsevents "~2.3.2"
classnames@^2.2.6:
version "2.3.2"
resolved "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz"
integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz"
@@ -639,7 +659,7 @@ csstype@^3.0.2:
resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz"
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
date-fns@^2.30.0:
date-fns@^2.0.1, date-fns@^2.30.0:
version "2.30.0"
resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz"
integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==
@@ -1148,7 +1168,7 @@ lodash.merge@^4.6.2:
resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
loose-envify@^1.1.0, loose-envify@^1.4.0:
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -1374,7 +1394,7 @@ prelude-ls@^1.2.1:
resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
prop-types@^15.5.0, prop-types@^15.6.2:
prop-types@^15.5.0, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.8.1"
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -1401,6 +1421,18 @@ react-async-script@^1.2.0:
hoist-non-react-statics "^3.3.0"
prop-types "^15.5.0"
react-datepicker@^4.20.0:
version "4.20.0"
resolved "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.20.0.tgz"
integrity sha512-I29yHN9SabUDSy7Xq3P8+E8E+D2vyeuYAYYWWjeMisGGtsatltV4CSHodyA7W9z0BuGycc/bhSClDbizx4gZHA==
dependencies:
"@popperjs/core" "^2.11.8"
classnames "^2.2.6"
date-fns "^2.30.0"
prop-types "^15.7.2"
react-onclickoutside "^6.13.0"
react-popper "^2.3.0"
react-dom@^18.2.0:
version "18.2.0"
resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz"
@@ -1409,6 +1441,11 @@ react-dom@^18.2.0:
loose-envify "^1.1.0"
scheduler "^0.23.0"
react-fast-compare@^3.0.1:
version "3.2.2"
resolved "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz"
integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==
react-google-recaptcha@^3.1.0:
version "3.1.0"
resolved "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-3.1.0.tgz"
@@ -1422,6 +1459,19 @@ react-is@^16.13.1, react-is@^16.7.0:
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-onclickoutside@^6.13.0:
version "6.13.0"
resolved "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz"
integrity sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==
react-popper@^2.2.5, react-popper@^2.3.0:
version "2.3.0"
resolved "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz"
integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==
dependencies:
react-fast-compare "^3.0.1"
warning "^4.0.2"
react-router-dom@^6.15.0:
version "6.15.0"
resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.15.0.tgz"
@@ -1703,6 +1753,13 @@ vite@^4.4.5:
optionalDependencies:
fsevents "~2.3.2"
warning@^4.0.2:
version "4.0.3"
resolved "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz"
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
dependencies:
loose-envify "^1.0.0"
which@^2.0.1:
version "2.0.2"
resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz"
+4 -4
View File
@@ -9,10 +9,10 @@ buildsRouter.get("/", async (_req, res) => {
res.json({ ok: 1 });
});
buildsRouter.post("/", async (req, res) => {
const build = await Build.create(req.body);
// buildsRouter.post("/", async (req, res) => {
// const build = await Build.create(req.body);
res.json(build);
});
// res.json(build);
// });
export default buildsRouter;
+34 -14
View File
@@ -83,6 +83,29 @@ companiesRouter.get(
}
);
companiesRouter.post(
"/:id/builds/:buildId/scheduled_sessions",
async (req, res) => {
if (req.params.id != res.locals.user.companyId) {
res.json({ error: "Access denied!" });
return;
}
if (!req.params.buildId) {
res.json({ error: "req.params.buildId" });
return;
}
const scheduledSession = await ScheduledSession.create({
companyId: req.params.id,
buildId: req.params.buildId,
...req.body,
});
res.json(scheduledSession);
}
);
companiesRouter.put(
"/:id/scheduled_sessions/:scheduledSessionId",
async (req, res) => {
@@ -140,6 +163,8 @@ companiesRouter.get(
req.params.scheduledSessionId
);
const scheduledSessionUserId = scheduledSession?.userId;
const scheduledSessionsAtSameTime = await ScheduledSession.find({
companyId: req.params.id,
buildId: req.params.buildId,
@@ -150,27 +175,22 @@ companiesRouter.get(
"users"
);
let userIds = users.map((user: any) => user.id);
const companyUserIds: any[] = users.map((user: any) => user.id);
const sessionsUserIds: any[] = [];
console.log(scheduledSession?.userId);
for (const userId of userIds) {
console.log(userId);
}
let sessionsUserIds: any[] = [];
for (const session of scheduledSessionsAtSameTime) {
if (session.userId) {
sessionsUserIds.push(session.userId.toString());
for (const sessionAtSameTime of scheduledSessionsAtSameTime) {
if (sessionAtSameTime.userId) {
sessionsUserIds.push(sessionAtSameTime.userId.toString());
}
}
const filteredUserIds = userIds.filter(
const filteredUserIds = companyUserIds.filter(
(userId: any) => !sessionsUserIds.includes(userId)
);
filteredUserIds.push(scheduledSession?.userId);
if (scheduledSessionUserId) {
filteredUserIds.push(scheduledSessionUserId.toString());
}
res.json(filteredUserIds);
} catch (error) {