This commit is contained in:
2024-06-12 20:06:30 +05:00
parent e155066534
commit a46547d1fe
19 changed files with 634 additions and 513 deletions
+79 -82
View File
@@ -3,109 +3,106 @@ import {
format,
getDaysInMonth,
getISODay,
isEqual,
isSameDay,
isToday,
setDate,
startOfDay,
startOfMonth,
subMonths,
} from "date-fns";
import { useEffect, useState } from "react";
import { useState } from "react";
import ChevronLeftIcon from "./icons/ChevronLeftIcon";
import ChevronRightIcon from "./icons/ChevronRightIcon";
import _ from "lodash";
import { ru } from "date-fns/locale";
import Button from "./Button";
import ChevronDownIcon from "./icons/ChevronDownIcon";
import ChevronUpIcon from "./icons/ChevronUpIcon";
import useSettingsStore from "../stores/useSettingsStore";
import useStore from "../stores/useStore";
interface Props {
defaultValue?: Date;
onChange?: (date: Date) => void;
}
function Calendar({ defaultValue, onChange }: Props) {
const [selectedDate, setSelectedDate] = useState<Date>(
startOfDay(defaultValue || new Date())
);
const [selectedMonth, setSelectedMonth] = useState<Date>(
startOfMonth(defaultValue || new Date())
);
function Calendar() {
const { selectedDay, setSelectedDay } = useStore();
const { isShowCalendar, setIsShowCalendar } = useSettingsStore();
const [currentMonth, setCurrentMonth] = useState<Date>(new Date());
function selectPrevMonth() {
setSelectedMonth(subMonths(selectedMonth, 1));
setCurrentMonth(subMonths(currentMonth, 1));
}
function selectNextMonth() {
setSelectedMonth(addMonths(selectedMonth, 1));
setCurrentMonth(addMonths(currentMonth, 1));
}
function hadndleClick(date: Date) {
setSelectedDate(date);
onChange && onChange(date);
}
useEffect(() => {
if (!defaultValue) return;
setSelectedDate(startOfDay(defaultValue));
}, [defaultValue]);
useEffect(() => {
setSelectedMonth(startOfMonth(selectedDate));
}, [selectedDate]);
return (
<div className="">
<div className="flex items-center justify-between">
<button
className="text-[#77828C] p-1 hover:bg-[#E6ECF2] rounded-lg transition-colors"
onClick={selectPrevMonth}
>
<ChevronLeftIcon />
</button>
<p className="text-sm font-semibold">
{_.capitalize(format(selectedMonth, "LLLL, yyyy", { locale: ru }))}
</p>
<button
className="text-[#77828C] p-1 hover:bg-[#E6ECF2] rounded-lg transition-colors"
onClick={selectNextMonth}
>
<ChevronRightIcon />
</button>
<div className="p-4 space-y-2 border-b border-[#DAE0E5]">
<div className="flex justify-between items-center">
<p className="text-sm font-semibold">Календарь</p>
<Button
color="tertiary"
icon={isShowCalendar ? <ChevronUpIcon /> : <ChevronDownIcon />}
onlyIcon
handleClick={() => setIsShowCalendar(!isShowCalendar)}
/>
</div>
<div className="grid grid-cols-7 gap-2">
{["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"].map((value, index) => (
<div
key={index}
className="w-8 h-8 flex items-center justify-center text-xs font-semibold"
>
{value}
</div>
))}
</div>
<div className="grid grid-cols-7 gap-2">
{Array.from({ length: getISODay(selectedMonth) - 1 }).map(
(_, index) => (
<div key={index} className="w-8 h-8"></div>
)
)}
{Array.from({ length: getDaysInMonth(selectedMonth) }).map(
(_, index) => (
{isShowCalendar && (
<div>
<div className="flex items-center justify-between">
<button
key={index}
className={`w-8 h-8 rounded-full flex items-center justify-center text-xs disabled:text-[#DAE0E5] ${
isEqual(setDate(selectedMonth, index + 1), selectedDate)
? "bg-[#49A1F5] text-white transition-colors"
: "enabled:hover:bg-[#E6ECF2]"
} ${
isToday(setDate(selectedMonth, index + 1))
? "border border-[#49A1F5]"
: ""
} `}
onClick={() => hadndleClick(setDate(selectedMonth, index + 1))}
className="text-[#77828C] p-1 hover:bg-[#E6ECF2] rounded-lg transition-colors"
onClick={selectPrevMonth}
>
{index + 1}
<ChevronLeftIcon />
</button>
)
)}
</div>
<p className="text-sm font-semibold">
{_.capitalize(format(currentMonth, "LLLL, yyyy", { locale: ru }))}
</p>
<button
className="text-[#77828C] p-1 hover:bg-[#E6ECF2] rounded-lg transition-colors"
onClick={selectNextMonth}
>
<ChevronRightIcon />
</button>
</div>
<div className="grid grid-cols-7 gap-2">
{["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"].map((value, index) => (
<div
key={index}
className="w-8 h-8 flex items-center justify-center text-xs font-semibold"
>
{value}
</div>
))}
</div>
<div className="grid grid-cols-7 gap-2">
{Array.from({ length: getISODay(currentMonth) - 1 }).map(
(_, index) => (
<div key={index} className="w-8 h-8"></div>
)
)}
{Array.from({ length: getDaysInMonth(currentMonth) }).map(
(_, index) => (
<button
key={index}
className={`w-8 h-8 rounded-full flex items-center justify-center text-xs disabled:text-[#DAE0E5] ${
isSameDay(setDate(currentMonth, index + 1), selectedDay)
? "bg-[#49A1F5] text-white transition-colors"
: "enabled:hover:bg-[#E6ECF2]"
} ${
isToday(setDate(currentMonth, index + 1))
? "border border-[#49A1F5]"
: ""
} `}
onClick={() =>
setSelectedDay(setDate(currentMonth, index + 1))
}
>
{index + 1}
</button>
)
)}
</div>
</div>
)}
</div>
);
}
+69 -37
View File
@@ -6,6 +6,10 @@ import MoreIcon from "./icons/MoreIcon";
import api from "../utils/api";
import Button from "./Button";
import { Link } from "react-router-dom";
import IUser from "../types/IUser";
import useAuthStore from "../stores/useAuthStore";
import EntryIcon from "./icons/EntryIcon";
import { isAfter } from "date-fns";
interface CardProps {
companyId: string;
@@ -17,13 +21,9 @@ interface CardProps {
phone: string;
email: string;
};
manager?: {
id: string;
name: string;
avatar: string;
};
managers: any[];
handleSelect: (scheduledSessionId: string, managerId: string) => void;
manager?: IUser;
managers: IUser[];
handleSelect: (scheduledSessionId: string, managerId: string | null) => void;
}
function Card({
@@ -36,8 +36,9 @@ function Card({
managers,
handleSelect,
}: CardProps) {
const { user } = useAuthStore();
const [isShow, setIsShow] = useState<boolean>(false);
const [availableManagers, setAvailableManagers] = useState<any[]>();
const [availableManagers, setAvailableManagers] = useState<IUser[]>();
async function getAvailableManagers() {
const result: any[] = await api
@@ -59,7 +60,7 @@ function Card({
}, [isShow]);
return (
<div className="relative flex flex-col gap-2">
<div className="flex flex-col gap-2">
<div className="w-[264px] h-[164px] px-3 py-2 bg-white border-r border-b border-[#DAE0E5] flex flex-col justify-between gap-2">
<div className="space-y-2">
<div className="flex justify-between">
@@ -83,7 +84,7 @@ function Card({
</div>
)}
</div>
<div className="border-b border-[#DAE0E5] pb-2">
<div>
<p className="text-xs text-[#77828C] leading-tight">
{client?.phone || "Телефон не указан"}
</p>
@@ -92,19 +93,16 @@ function Card({
</p>
</div>
</div>
<div className="flex justify-between items-center py-1">
<div className=" flex items-center gap-2">
<div className="flex justify-between items-center pt-2 py-1 border-t border-[#DAE0E5] h-[45px]">
<div className="flex items-center gap-2">
{manager ? (
<div className="relative">
<img
src={manager.avatar}
alt=""
className="w-6 h-6 rounded-full"
/>
{/* <div className="absolute right-0 bottom-0 bg-[#27AE60] border border-white rounded-full w-2 h-2"></div> */}
</div>
<img
src={manager.avatar}
alt=""
className="w-6 h-6 rounded-full"
/>
) : (
<div className="w-6 h-6 bg-[#ccc] rounded-full"></div>
<div className="w-6 h-6 bg-[#E6ECF2] rounded-full"></div>
)}
<p className="text-xs font-semibold">
{manager ? manager.name : "Не назначен"}
@@ -112,27 +110,61 @@ function Card({
</div>
<div className="flex gap-2">
{manager && (
<Link
to={`${
import.meta.env.VITE_STREAM_URL
}/scheduled/${scheduledSessionId}?admin=true`}
target="_blank"
>
<Button>Начать</Button>
</Link>
{user?.role === "manager" &&
(manager ? (
isAfter(new Date(), new Date(scheduleSessionStartAt)) ? (
<Link
to={`${
import.meta.env.VITE_STREAM_URL
}/scheduled/${scheduledSessionId}?admin=true`}
target="_blank"
>
<Button>Начать</Button>
</Link>
) : (
manager.id === user.id && (
<Button
color="secondary"
handleClick={() => handleSelect(scheduledSessionId, null)}
>
Отменить
</Button>
)
)
) : (
<Button
color="secondary"
handleClick={() => handleSelect(scheduledSessionId, user.id)}
>
Выбрать
</Button>
))}
{user?.role === "admin" && (
<div className="flex gap-1">
{manager && (
<Link
to={`${
import.meta.env.VITE_STREAM_URL
}/scheduled/${scheduledSessionId}?admin=true`}
target="_blank"
>
<Button color="secondary" icon={<EntryIcon />} onlyIcon />
</Link>
)}
<button
onClick={() => setIsShow(!isShow)}
className="p-1 text-[#77828C] hover:bg-neutral-100 rounded-lg"
>
<MoreIcon />
</button>
</div>
)}
<button
onClick={() => setIsShow(!isShow)}
className="p-1 text-[#77828C] hover:bg-neutral-100 rounded-lg"
>
<MoreIcon />
</button>
</div>
</div>
</div>
<div className="absolute bottom-[2px] left-[272px] z-10">
<div className="absolute z-10 pl-[calc(264px+8px)]">
{availableManagers && availableManagers.length > 0 && (
<SelectUser
shown={isShow}
+1 -1
View File
@@ -47,7 +47,7 @@ function Input({
value={defaultValue}
onChange={(e) => setValue(e.target.value)}
onFocus={handleFocus}
className={`px-3 py-2.5 outline-none rounded-lg border border-[#DAE0E5] focus:border-[#49A1F5] transition-colors ${className}`}
className={`px-3 py-2.5 outline-none rounded-lg border border-[#DAE0E5] focus:border-[#49A1F5] transition-colors text-sm ${className}`}
/>
);
}
+67
View File
@@ -0,0 +1,67 @@
import useAuthStore from "../stores/useAuthStore";
import useSettingsStore from "../stores/useSettingsStore";
import useStore from "../stores/useStore";
import Button from "./Button";
import ChevronDownIcon from "./icons/ChevronDownIcon";
import ChevronUpIcon from "./icons/ChevronUpIcon";
import MoreIcon from "./icons/MoreIcon";
function Managers() {
const { user } = useAuthStore();
const { managers } = useStore();
const { isShowManagers, setIsShowManagers } = useSettingsStore();
return (
<div className="p-4 flex flex-col gap-4 border-b border-[#DAE0E5]">
<div className="flex justify-between items-center">
<p className="text-sm font-semibold">Менеджеры</p>
<Button
color="tertiary"
icon={isShowManagers ? <ChevronUpIcon /> : <ChevronDownIcon />}
onlyIcon
handleClick={() => setIsShowManagers(!isShowManagers)}
/>
</div>
{isShowManagers && (
<div className="space-y-4">
<div className="space-y-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">{manager.name}</p>
</div>
{user?.role === "admin" && (
<Button
disabled
color="tertiary"
onlyIcon
icon={<MoreIcon />}
handleClick={() => {}}
/>
)}
</div>
))}
</div>
{user?.role === "admin" && (
<Button
disabled
color="secondary"
className="w-full"
handleClick={() => {}}
>
Добавить
</Button>
)}
</div>
)}
</div>
);
}
export default Managers;
+21 -12
View File
@@ -6,32 +6,39 @@ import ParamsIcon from "./icons/ParamsIcon";
import WorkIcon from "./icons/WorkIcon";
import ExitIcon from "./icons/ExitIcon";
import useAuthStore from "../stores/useAuthStore";
import { useClickAway } from "@uidotdev/usehooks";
function Menu() {
const [isShow, setIsShow] = useState<boolean>(false);
const { user, setAccessToken } = useAuthStore();
const ref = useClickAway<HTMLDivElement>(() => {
setIsShow(false);
});
function logout() {
setAccessToken(null);
}
return (
<div className="">
<button
onClick={() => setIsShow((prev) => !prev)}
className={`p-3 transition-colors relative z-20 ${
isShow ? "bg-[#49A1F5] text-white" : "hover:bg-neutral-200"
}`}
>
<BurgerIcon />
</button>
<div>
<span className="relative cursor-pointer z-20">
<button
onClick={() => setIsShow(true)}
className={`p-3 transition-colors relative z-20 ${
isShow
? "bg-[#49A1F5] text-white pointer-events-none"
: "hover:bg-neutral-200"
}`}
>
<BurgerIcon />
</button>
</span>
<Transition in={isShow} timeout={150} mountOnEnter unmountOnExit>
{(state) => (
<div className={`transition-opacity ${state}`}>
<div className="absolute top-0 left-0 w-full h-full bg-black bg-opacity-10 z-10"></div>
<div className="absolute z-20 ml-2 mt-3.5">
<div ref={ref} className="absolute z-20 ml-2 mt-3.5">
<div className="relative">
<svg
width="12"
@@ -49,7 +56,9 @@ function Menu() {
<div className="relative w-[240px] bg-white rounded-lg shadow">
<div className="border-b border-[#DAE0E5] p-6 flex flex-col items-center justify-center gap-4">
<div className="rounded-full bg-[#E6ECF2] w-[88px] h-[88px] flex justify-center items-center">
<p className="text-2xl font-semibold ml-0.5 mt-0.5">{user?.name[0]}</p>
<p className="text-2xl font-semibold ml-0.5 mt-0.5">
{user?.name[0]}
</p>
</div>
<div className="space-y-1 text-center">
<p className="text-sm">{user?.name}</p>
+84
View File
@@ -0,0 +1,84 @@
import { format } from "date-fns";
import Button from "./Button";
import CreateScheduleModal from "./modals/CreateScheduleModal";
import useModalStore from "../stores/useModalStore";
import useSettingsStore from "../stores/useSettingsStore";
import ChevronDownIcon from "./icons/ChevronDownIcon";
import ChevronUpIcon from "./icons/ChevronUpIcon";
import useStore from "../stores/useStore";
import useAuthStore from "../stores/useAuthStore";
function Schedules() {
const { user } = useAuthStore();
const { schedules } = useStore();
const { setModal } = useModalStore();
const { isShowSchedules, setIsShowSchedules } = useSettingsStore();
return (
<div className="p-4 flex flex-col gap-4 border-b border-[#DAE0E5]">
<div className="flex justify-between items-center">
<p className="text-sm font-semibold">Расписание</p>
<Button
color="tertiary"
icon={isShowSchedules ? <ChevronUpIcon /> : <ChevronDownIcon />}
onlyIcon
handleClick={() => setIsShowSchedules(!isShowSchedules)}
/>
</div>
{isShowSchedules && (
<div className="space-y-4">
{schedules?.map((schedule) => (
<div key={schedule.id} className="flex flex-col gap-3 text-xs">
<p className="font-semibold flex gap-1">
<span>
Действует с{" "}
{format(new Date(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.sessionsPerDay}</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>
))}
{user?.role === "admin" && (
<Button
color="secondary"
className="w-full"
handleClick={() => setModal(<CreateScheduleModal />)}
>
Добавить
</Button>
)}
</div>
)}
</div>
);
}
export default Schedules;
+20 -13
View File
@@ -4,7 +4,7 @@ interface SelectUserProps {
shown: boolean;
selectedManagerId?: string;
managers: any[];
handleClick: (managerId: string) => void;
handleClick: (managerId: string | null) => void;
handleShown: () => void;
}
@@ -26,7 +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 shadow-md transition-opacity ${state}`}
className={`bg-white w-[280px] max-h-[126px] overflow-auto rounded-lg py-2 flex flex-col gap-1 shadow transition-opacity ${state}`}
>
{managers.map((manager) => (
<button
@@ -35,17 +35,14 @@ function SelectUser({
className="px-4 flex justify-between gap-2 w-full py-1 transition-colors hover:bg-[#E6ECF2]"
>
<div className="flex items-center gap-2">
<div className="relative">
<img
src={
manager.avatar ||
"https://as2.ftcdn.net/v2/jpg/00/64/67/27/1000_F_64672736_U5kpdGs9keUll8CRQ3p3YaEv2M6qkVY5.jpg"
}
alt=""
className="w-6 h-6 rounded-full"
/>
{/* <div className="absolute right-0 bottom-0 bg-[#27AE60] border border-white rounded-full w-2 h-2"></div> */}
</div>
<img
src={
manager.avatar ||
"https://as2.ftcdn.net/v2/jpg/00/64/67/27/1000_F_64672736_U5kpdGs9keUll8CRQ3p3YaEv2M6qkVY5.jpg"
}
alt=""
className="w-6 h-6 rounded-full"
/>
<p className="text-xs">{manager.name}</p>
</div>
{manager.id === selectedManagerId && (
@@ -55,6 +52,16 @@ function SelectUser({
)}
</button>
))}
<button
onClick={() => handleClick(null)}
className="px-4 flex justify-between gap-2 w-full py-1 transition-colors hover:bg-[#E6ECF2]"
>
<div className="flex items-center gap-2">
<div className="w-6 h-6 rounded-full bg-[#E6ECF2]"></div>
<p className="text-xs">Без менеджера</p>
</div>
</button>
</div>
)}
</Transition>
+21
View File
@@ -0,0 +1,21 @@
function EntryIcon() {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.5 20L17 20C18.1046 20 19 19.1046 19 18L19 6C19 4.89543 18.1046 4 17 4L8.5 4M15.4998 12L5 11.9997M15.4998 12L11.9998 15.5556M15.4998 12L11.9998 8.44444"
stroke="currentColor"
strokeWidth={1.5}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
export default EntryIcon;
@@ -20,20 +20,11 @@ import ChoiceChips from "../ChoiceChips";
// import ISchedule from "../../types/ISchedule";
import DatePicker from "../DatePicker";
import IScheduledSession from "../../types/IScheduledSession";
import useStore from "../../stores/useStore";
import ISchedule from "../../types/ISchedule";
interface Props {
companyId: string;
buildId: string;
// schedules: ISchedule[];
handleCreate: () => void;
}
function CreateScheduleModal({
companyId,
buildId,
// schedules,
handleCreate,
}: Props) {
function CreateScheduleModal() {
const { company, selectedBuild, schedules, setSchedules } = useStore();
const setModal = useModalStore((state) => state.setModal);
const [date, setDate] = useState<Date>(startOfDay(new Date()));
const [startDate, setStartDate] = useState<Date>(startOfDay(new Date()));
@@ -47,7 +38,9 @@ function CreateScheduleModal({
async function getLastScheduledSessionDate() {
try {
const result: IScheduledSession | null = await api
.get(`companies/${companyId}/builds/${buildId}/last_scheduled_session`)
.get(
`companies/${company?.id}/builds/${selectedBuild?.id}/last_scheduled_session`
)
.json();
if (!result || !result.startAt) return;
@@ -84,27 +77,33 @@ function CreateScheduleModal({
}
}
async function createSchedule() {
console.log("date", date);
async function addSchedule() {
try {
const result: ISchedule = await api
.post(
`companies/${company?.id}/builds/${selectedBuild?.id}/schedules`,
{
json: {
startDate: date,
startTime,
endTime,
weekends,
sessionDuration,
sessionBreak,
sessionsPerDay,
},
}
)
.json();
await api.post(`companies/${companyId}/builds/${buildId}/schedules`, {
json: {
startDate: date,
startTime,
endTime,
weekends,
sessionDuration,
sessionBreak,
sessionsPerDay,
},
});
setSchedules([...schedules, result]);
} catch (error) {
alert((error as Error).message);
}
}
async function handleClickCreateSchedule() {
// if (!startDate) return;
await createSchedule();
handleCreate();
await addSchedule();
setModal(null);
}
+107 -332
View File
@@ -1,52 +1,53 @@
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useEffect, useRef, useState } from "react";
import Card from "../components/Card";
import EmptyCard from "../components/EmptyCard";
import TabButton from "../components/TabButton";
import BurgerIcon from "../components/icons/BurgerIcon";
import ChevronLeftIcon from "../components/icons/ChevronLeftIcon";
import ChevronRightIcon from "../components/icons/ChevronRightIcon";
import api from "../utils/api";
import useAuthStore from "../stores/useAuthStore";
import {
eachMinuteOfInterval,
format,
parse,
addDays,
subDays,
isAfter,
isEqual,
} from "date-fns";
import Button from "../components/Button";
import { useEffect, useRef, useState } from "react";
import Card from "../components/Card";
import EmptyCard from "../components/EmptyCard";
import TabButton from "../components/TabButton";
import api from "../utils/api";
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 CreateScheduleModal from "../components/modals/CreateScheduleModal";
import MoreIcon from "../components/icons/MoreIcon";
import ISchedule from "../types/ISchedule";
import Calendar from "../components/Calendar";
import ChevronUpIcon from "../components/icons/ChevronUpIcon";
import { isEqual } from "lodash";
import _ from "lodash";
import useStore from "../stores/useStore";
import useAuthStore from "../stores/useAuthStore";
import Menu from "../components/Menu";
import Button from "../components/Button";
import Calendar from "../components/Calendar";
import Managers from "../components/Managers";
import Schedules from "../components/Schedules";
import SpinnerIcon from "../components/icons/SpinnerIcon";
import ModalContainer from "../components/ModalContainer";
import ChevronLeftIcon from "../components/icons/ChevronLeftIcon";
import ChevronRightIcon from "../components/icons/ChevronRightIcon";
function DashboardPage() {
const [user, setAccessToken] = useAuthStore((state) => [
state.user,
state.setAccessToken,
]);
const [company, setCompany] = useState<{ [key: string]: any }>();
const [selectedBuildManagers, setSelectedBuildManagers] = useState<any[]>();
const [builds, setBuilds] = useState<any[]>();
const [selectedBuild, setSelectedBuild] = useState<{ [key: string]: any }>();
const [schedules, setSchedules] = useState<ISchedule[]>();
const { user } = useAuthStore();
const {
company,
setCompany,
selectedBuild,
setSelectedBuild,
builds,
setBuilds,
managers,
setManagers,
schedules,
setSchedules,
selectedDay,
setSelectedDay,
} = useStore();
const [duration, setDuration] = useState<number>();
const [scheduledSessions, setScheduledSessions] = useState<any[]>();
const [generatedScheduledSessions, setGeneratedScheduledSessions] =
useState<any[]>();
const [selectedDate, setSelectedDate] = useState(new Date());
const [dateTimes, setDateTimes] = useState<Date[]>();
const [currentTime, setCurrentTime] = useState<string>(
format(new Date(), "HH:mm")
@@ -54,63 +55,19 @@ 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 })
// );
function selectNextDay() {
setSelectedDate((prev) => addDays(prev, 1));
setSelectedDay(addDays(selectedDay, 1));
}
function selectPrevDay() {
setSelectedDate((prev) => subDays(prev, 1));
setSelectedDay(subDays(selectedDay, 1));
}
function selectCurrentDate() {
setSelectedDate(new Date());
setSelectedDay(new Date());
}
// useEffect(() => {
// console.log("schedules", schedules);
// if (!selectedDate || !selectedBuild || !schedules?.length) return;
// const foundSchedule = schedules.find(
// (schedule) =>
// isWithinInterval(selectedDate, {
// start: new Date(schedule.startDate),
// end: addDays(new Date(schedule.startDate), 14),
// }) && selectedBuild.id === schedule.buildId
// );
// console.log("foundSchedule", foundSchedule);
// if (foundSchedule) {
// const startDateTime = parse(
// foundSchedule.startTime,
// "HH:mm",
// selectedDate
// );
// const endDateTime = parse(foundSchedule.endTime, "HH:mm", selectedDate);
// const step = foundSchedule.sessionDuration + foundSchedule.sessionBreak;
// setDateTimes(
// eachMinuteOfInterval(
// {
// start: startDateTime,
// end: endDateTime,
// },
// { step }
// )
// );
// } else {
// setDateTimes(undefined);
// setGeneratedScheduledSessions(undefined);
// }
// }, [selectedDate, selectedBuild, schedules]);
function findSessionsByTime(time: Date) {
if (!scheduledSessions) return [];
@@ -122,6 +79,8 @@ function DashboardPage() {
}
function generateScheduledSessions() {
if (!selectedBuild) return;
const newGeneratedScheduledSession: any[] = [];
dateTimes?.forEach((time) => {
@@ -134,7 +93,7 @@ function DashboardPage() {
for (
let index = 0;
index < selectedBuild?.sessionLimit - foundSessionsByTime.length;
index < selectedBuild.sessionLimit - foundSessionsByTime.length;
index++
) {
sessions.push({});
@@ -150,36 +109,36 @@ function DashboardPage() {
}
useEffect(() => {
if (!scheduledSessions) return;
if (!scheduledSessions || !selectedBuild || !selectedDay) return;
generateScheduledSessions();
}, [scheduledSessions]);
}, [scheduledSessions, selectedBuild, selectedDay]);
useEffect(() => {
if (!schedules || !selectedDate || !selectedBuild) return;
if (!schedules || !selectedDay || !selectedBuild) return;
const foundSchedule = schedules.find((schedule) =>
isAfter(new Date(), new Date(schedule.startDate))
console.log("schedules", schedules);
const foundSchedule = schedules.find(
(schedule) => schedule.buildId === selectedBuild.id
);
if (!foundSchedule) return;
if (!foundSchedule) {
setDateTimes([]);
return;
}
const step = foundSchedule.sessionDuration + foundSchedule.sessionBreak; // 35
const newTimes = eachMinuteOfInterval(
{
start: parse(foundSchedule.startTime, "HH:mm", new Date(selectedDate)), // 11:00
end: parse(foundSchedule.endTime, "HH:mm", new Date(selectedDate)), // 20:00
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, selectedDate, selectedBuild]);
useEffect(() => {
if (!schedules) return;
// const schedule = schedules.find(schedule => schedule.startDate);
}, [selectedDate, selectedBuild, schedules]);
}, [schedules, selectedDay, selectedBuild]);
async function getCompany() {
if (!user) {
@@ -224,7 +183,7 @@ function DashboardPage() {
const result: any = await api
.get(`companies/${company.id}/builds/${selectedBuild.id}/users`)
.json();
setSelectedBuildManagers(result);
setManagers(result);
} catch (error) {
if (error instanceof Error) {
console.log("Error: ", error.message);
@@ -250,7 +209,7 @@ function DashboardPage() {
.get(
`companies/${company.id}/builds/${
selectedBuild.id
}/scheduled_sessions?date=${selectedDate.toISOString()}`
}/scheduled_sessions?date=${selectedDay.toISOString()}`
)
.json();
@@ -264,39 +223,9 @@ function DashboardPage() {
if (useLoader) setIsLoadingScheduledSessions(false);
}
// function generateScheduledSessions() {
// if (!dateTimes || !scheduledSessions || !selectedBuild) return;
// const arr: any[][] = [];
// dateTimes.forEach((dateTime) => {
// const arr2 = [];
// const foundSessionsCount = scheduledSessions.filter((session) =>
// isEqual(session.startAt, dateTime)
// );
// arr2.push(dateTime);
// for (let i = 0; i < selectedBuild.sessionLimit; i++) {
// if (foundSessionsCount[i]) {
// arr2.push(foundSessionsCount[i]);
// } else {
// arr2.push({});
// }
// }
// arr.push(arr2);
// console.log("arr2", arr2);
// });
// setGeneratedScheduledSessions(arr);
// }
async function updateScheduledSessionManager(
scheduledSessionId: string,
managerId: string
managerId: string | null
) {
if (!company || !scheduledSessions) return;
@@ -325,6 +254,8 @@ function DashboardPage() {
async function getSchedules() {
if (!company || !selectedBuild) return;
console.log("selectedBuild.id", selectedBuild.id);
const result: any[] = await api
.get(`companies/${company.id}/builds/${selectedBuild.id}/schedules`)
.json();
@@ -355,33 +286,28 @@ function DashboardPage() {
}, [builds]);
useEffect(() => {
if (!selectedBuildManagers || !selectedDate || !selectedBuild) return;
if (!managers || !selectedDay || !selectedBuild) return;
getScheduledSessions(true);
const interval = setInterval(() => {
getScheduledSessions();
}, 3000);
}, 1000);
return () => {
clearInterval(interval);
};
}, [selectedBuildManagers, selectedDate, selectedBuild]);
}, [managers, selectedDay, selectedBuild]);
useEffect(() => {
if (!company || !selectedBuild) return;
getManagers();
console.log("selectedBuild", selectedBuild);
getSchedules();
getManagers();
}, [selectedBuild]);
// useEffect(() => {
// if (!scheduledSessions) return;
// generateScheduledSessions();
// }, [scheduledSessions]);
useEffect(() => {
// setIsLoadingScheduledSessions(true);
scheduledSessionsRef.current?.scrollTo({ top: 0, behavior: "smooth" });
}, [selectedDate, selectedBuild]);
}, [selectedDay, selectedBuild]);
return (
<div className="main h-screen flex">
@@ -392,7 +318,9 @@ function DashboardPage() {
<TabButton
key={build.id}
handleClick={() => setSelectedBuild(build)}
active={selectedBuild && build.id === selectedBuild.id}
active={
(selectedBuild && build.id === selectedBuild?.id) || false
}
>
{build.name}
</TabButton>
@@ -403,7 +331,7 @@ function DashboardPage() {
<p className="text-sm font-semibold">Расписание</p>
<div className="flex items-center gap-4">
<p className="text-sm font-semibold">
{format(selectedDate, "PPPP", { locale: ru })}
{format(selectedDay, "PPPP", { locale: ru })}
</p>
<div className="flex gap-1">
<Button
@@ -465,102 +393,50 @@ function DashboardPage() {
<p>{format(generatedScheduledSession.time, "HH:mm")}</p>
</div>
<div className="flex">
{generatedScheduledSession.sessions.map(
(session: any, index2: number) => {
const selectedManager = selectedBuildManagers?.find(
(manager) => manager.id == session.userId
);
{company &&
managers &&
selectedBuild &&
generatedScheduledSession.sessions.map(
(session: any, index2: number) => {
const selectedManager = managers.find(
(manager) => manager.id == session.userId
);
if (!_.isEmpty(session)) {
return (
<Card
key={index2}
companyId={company?.id}
buildId={selectedBuild?.id}
scheduledSessionId={session.id}
scheduleSessionStartAt={session.startAt}
client={session.client}
manager={selectedManager}
managers={selectedBuildManagers || []}
handleSelect={(scheduledSessionId, managerId) =>
updateScheduledSessionManager(
scheduledSessionId,
managerId
)
}
/>
);
} else {
return (
<EmptyCard
key={index2}
buildId={selectedBuild?.id}
startAt={generatedScheduledSession.time}
duration={duration!}
/>
);
if (!_.isEmpty(session)) {
return (
<Card
key={index2}
companyId={company.id}
buildId={selectedBuild.id}
scheduledSessionId={session.id}
scheduleSessionStartAt={session.startAt}
client={session.client}
manager={selectedManager}
managers={managers}
handleSelect={(scheduledSessionId, managerId) =>
updateScheduledSessionManager(
scheduledSessionId,
managerId
)
}
/>
);
} else {
return (
<EmptyCard
key={index2}
buildId={selectedBuild?.id}
startAt={generatedScheduledSession.time}
duration={duration!}
/>
);
}
}
}
)}
)}
</div>
</div>
)
)}
{/* {company &&
selectedBuild &&
user &&
generatedScheduledSessions?.map(
(generatedScheduledSession, index) => {
return (
<div key={index} className="flex">
{generatedScheduledSession.map(
(scheduledSession, index2) => {
if (index2 === 0) {
return (
<div
key={index2}
className="w-[84px] h-[164px] flex justify-center items-center text-sm font-semibold bg-[#F0F1F2] border-r border-b border-[#DAE0E5]"
>
{format(scheduledSession, "HH:mm")}
</div>
);
} else {
if (Object.keys(scheduledSession).length) {
const selectedManager = selectedBuildManagers?.find(
(manager) => manager.id == scheduledSession.userId
);
return (
<Card
key={index2}
companyId={company.id}
buildId={selectedBuild.id}
scheduledSessionId={scheduledSession.id}
scheduleSessionStartAt={
scheduledSession.startAt
}
client={scheduledSession.client}
manager={selectedManager}
managers={selectedBuildManagers || []}
handleSelect={(scheduledSessionId, managerId) =>
updateScheduledSessionManager(
scheduledSessionId,
managerId
)
}
/>
);
} else {
return <EmptyCard key={index2} />;
}
}
}
)}
</div>
);
}
)} */}
</div>
</div>
@@ -576,110 +452,9 @@ function DashboardPage() {
</button>
</div>
<div className="overflow-y-auto overflow-x-hidden">
<div className="p-4 space-y-2">
<div className="flex justify-between items-center">
<p className="text-sm font-semibold">Календарь</p>
<Button color="tertiary" icon={<ChevronUpIcon />} onlyIcon />
</div>
<Calendar
defaultValue={selectedDate}
onChange={(date) => setSelectedDate(date)}
/>
</div>
<div className="p-4 flex flex-col gap-4">
<p className="text-sm font-semibold">Расписание</p>
{schedules?.map((schedule) => (
<div key={schedule.id} className="flex flex-col gap-3 text-xs">
<p className="font-semibold flex gap-1">
<span>
Действует с{" "}
{format(new Date(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.sessionsPerDay}</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(
<CreateScheduleModal
companyId={company?.id}
buildId={selectedBuild?.id}
// schedules={schedules}
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">
{selectedBuildManagers?.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>
</div>
<Calendar />
<Schedules />
<Managers />
</div>
</div>
+4 -3
View File
@@ -1,19 +1,20 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
import IUser from "../types/IUser";
interface AuthState {
accessToken: string | null;
user: Record<string, never> | null;
setAccessToken: (accessToken: string | null) => void;
setUser: (user: Record<string, never> | null) => void;
user: IUser | null;
setUser: (user: IUser | null) => void;
}
const useAuthStore = create<AuthState>()(
persist(
(set) => ({
accessToken: null,
user: null,
setAccessToken: (accessToken) => set({ accessToken }),
user: null,
setUser: (user) => set({ user }),
}),
{
+29
View File
@@ -0,0 +1,29 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
interface SettingsState {
isShowCalendar: boolean;
setIsShowCalendar: (isShowCalendar: boolean) => void;
isShowSchedules: boolean;
setIsShowSchedules: (isShowSchedules: boolean) => void;
isShowManagers: boolean;
setIsShowManagers: (isShowManagers: boolean) => void;
}
const useSettingsStore = create<SettingsState>()(
persist(
(set) => ({
isShowCalendar: true,
setIsShowCalendar: (isShowCalendar) => set({ isShowCalendar }),
isShowSchedules: true,
setIsShowSchedules: (isShowSchedules) => set({ isShowSchedules }),
isShowManagers: true,
setIsShowManagers: (isShowManagers) => set({ isShowManagers }),
}),
{
name: "settings",
}
)
);
export default useSettingsStore;
+37
View File
@@ -0,0 +1,37 @@
import { create } from "zustand";
import ICompany from "../types/ICompany";
import IBuild from "../types/IBuild";
import ISchedule from "../types/ISchedule";
import IUser from "../types/IUser";
interface State {
company: ICompany | null;
setCompany: (company: ICompany) => void;
builds: IBuild[];
setBuilds: (builds: IBuild[]) => void;
managers: IUser[];
setManagers: (managers: IUser[]) => void;
schedules: ISchedule[];
setSchedules: (schedules: ISchedule[]) => void;
selectedBuild: IBuild | null;
setSelectedBuild: (selectedBuild: IBuild) => void;
selectedDay: Date;
setSelectedDay: (selectedDay: Date) => void;
}
const useStore = create<State>((set) => ({
company: null,
setCompany: (company) => set({ company }),
builds: [],
setBuilds: (builds) => set({ builds }),
managers: [],
setManagers: (managers) => set({ managers }),
schedules: [],
setSchedules: (schedules) => set({ schedules }),
selectedBuild: null,
setSelectedBuild: (selectedBuild) => set({ selectedBuild }),
selectedDay: new Date(),
setSelectedDay: (selectedDay) => set({ selectedDay }),
}));
export default useStore;
+9
View File
@@ -0,0 +1,9 @@
interface IBuild {
id: string;
companyId: string;
build: string;
name: string;
sessionLimit: number;
}
export default IBuild;
+6
View File
@@ -0,0 +1,6 @@
interface ICompany {
id: string;
name: string;
}
export default ICompany;
+10
View File
@@ -0,0 +1,10 @@
interface IUser {
id: string;
companyId: string;
avatar: string;
name: string;
username: string;
role: string;
}
export default IUser;