upd
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
) : (
|
||||
|
||||
@@ -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}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,9 +422,27 @@ 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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user