From b52096b3bc7f13a5b4b8077f7bafe67ec6c15d9b Mon Sep 17 00:00:00 2001 From: SD Date: Thu, 5 Sep 2024 20:06:00 +0400 Subject: [PATCH] feat: add edit modal for expert's schedule --- .../account/(account)/expert-profile/page.tsx | 18 +- src/app/[locale]/experts/[expertId]/page.tsx | 3 +- src/app/sitemap.jsx | 4 +- .../ExpertProfile/ExpertProfile.tsx | 8 +- .../ExpertProfile/content/ExpertAbout.tsx | 18 +- .../ExpertProfile/content/ExpertSchedule.tsx | 30 ++- .../Modals/EditExpertScheduleModal.tsx | 214 ++++++++++++++++++ src/components/view/CustomMultiSelect.tsx | 4 +- src/components/view/CustomSelect.tsx | 8 +- src/components/view/CustomTimePicker.tsx | 48 ++++ src/constants/time.ts | 37 +++ src/i18nKeys/de.ts | 1 + src/i18nKeys/en.ts | 1 + src/i18nKeys/es.ts | 1 + src/i18nKeys/fr.ts | 1 + src/i18nKeys/it.ts | 1 + src/i18nKeys/ru.ts | 1 + src/styles/_pages.scss | 12 + src/styles/view/_select.scss | 14 ++ src/styles/view/_timepicker.scss | 59 +++++ src/styles/view/style.scss | 1 + src/types/profile.ts | 8 +- src/types/schedule.ts | 9 +- src/utils/time.ts | 204 +++++++++++++++-- 24 files changed, 637 insertions(+), 68 deletions(-) create mode 100644 src/components/Modals/EditExpertScheduleModal.tsx create mode 100644 src/components/view/CustomTimePicker.tsx create mode 100644 src/constants/time.ts create mode 100644 src/styles/view/_timepicker.scss diff --git a/src/app/[locale]/account/(account)/expert-profile/page.tsx b/src/app/[locale]/account/(account)/expert-profile/page.tsx index 3eaea64..7cda5f2 100644 --- a/src/app/[locale]/account/(account)/expert-profile/page.tsx +++ b/src/app/[locale]/account/(account)/expert-profile/page.tsx @@ -38,10 +38,6 @@ export default function ExpertProfilePage({ params: { locale } }: { params: { lo getPayData(locale, jwt) ]) .then(([profile, person, education, tags, practice, schedule, payData]) => { - console.log('profile', profile); - console.log('person', person); - console.log('education', education); - console.log('schedule', schedule); setIsFull(profile.fillProgress === 'full'); setData({ person, @@ -63,14 +59,12 @@ export default function ExpertProfilePage({ params: { locale } }: { params: { lo return ( - {data && ( - - )} + ); }; diff --git a/src/app/[locale]/experts/[expertId]/page.tsx b/src/app/[locale]/experts/[expertId]/page.tsx index fe65fc6..3da5bee 100644 --- a/src/app/[locale]/experts/[expertId]/page.tsx +++ b/src/app/[locale]/experts/[expertId]/page.tsx @@ -9,7 +9,7 @@ import { ExpertInformation, ExpertPractice } from '../../../../components/Experts/ExpertDetails'; -import { Details } from '../../../../types/experts'; +import { Details } from '../../../../types/education'; import { BackButton } from '../../../../components/view/BackButton'; import { i18nText } from '../../../../i18nKeys'; @@ -36,7 +36,6 @@ export default async function ExpertItem({ params: { expertId = '', locale } }: if (!expertId) notFound(); const expert = await getExpertById(expertId, locale); - console.log(expert); const getAssociationLevel = (accLevelId?: number) => { if (accLevelId) { diff --git a/src/app/sitemap.jsx b/src/app/sitemap.jsx index 84e710a..4dd1000 100644 --- a/src/app/sitemap.jsx +++ b/src/app/sitemap.jsx @@ -1,4 +1,4 @@ -import {fetchBlogPosts} from "../lib/contentful/blogPosts"; +import { fetchBlogPosts } from '../lib/contentful/blogPosts'; export default async function sitemap() { const paths = [ @@ -24,4 +24,4 @@ export default async function sitemap() { }) return paths -} \ No newline at end of file +} diff --git a/src/components/ExpertProfile/ExpertProfile.tsx b/src/components/ExpertProfile/ExpertProfile.tsx index fa7e13a..c7e1e71 100644 --- a/src/components/ExpertProfile/ExpertProfile.tsx +++ b/src/components/ExpertProfile/ExpertProfile.tsx @@ -145,7 +145,13 @@ export const ExpertProfile = ({ locale, data, updateData, isFull }: ExpertProfil updateExpert={updateExpert} /> - + + + void; }; -export const ExpertSchedule = ({ locale, data }: ExpertScheduleProps) => { +export const ExpertSchedule = ({ locale, data, updateExpert }: ExpertScheduleProps) => { const [showEdit, setShowEdit] = useState(false); - // person51 return (

{i18nText('schedule', locale)}

- {/*} onClick={() => setShowEdit(true)} - />*/} + />
{data && data?.workingTimes?.map((date, index) => { - const { startDay, startTime, endDay, endTime } = getCurrentTime(date); + const { startDay, startTimeMin, endTimeMin } = getCurrentTime(date, dayjs().format('Z')); return (
{i18nText(startDay, locale)} -
{startTime}
+
{startTimeMin ? getTimeString(startTimeMin) : '00:00'}
- - {startDay !== endDay && {i18nText(endDay, locale)}} -
{endTime}
+
{endTimeMin ? getTimeString(endTimeMin) : '00:00'}
) })}
+ setShowEdit(false)} + locale={locale} + data={data} + refresh={() => updateExpert('schedule')} + />
); }; diff --git a/src/components/Modals/EditExpertScheduleModal.tsx b/src/components/Modals/EditExpertScheduleModal.tsx new file mode 100644 index 0000000..58e516d --- /dev/null +++ b/src/components/Modals/EditExpertScheduleModal.tsx @@ -0,0 +1,214 @@ +'use client'; + +import React, { FC, useEffect, useState } from 'react'; +import { Modal, Button, message } from 'antd'; +import { CloseOutlined, DeleteOutlined } from '@ant-design/icons'; +import dayjs from 'dayjs'; +import { i18nText } from '../../i18nKeys'; +import { AUTH_TOKEN_KEY } from '../../constants/common'; +import { UTC_LIST } from '../../constants/time'; +import { MapWorkingTime, ScheduleDTO } from '../../types/schedule'; +import { + WEEK_DAY, + formattedSchedule, + getNewTime, + getTimeZoneOffset, + getTimeString, + formattedTimeByOffset, formattedWorkList +} from '../../utils/time'; +import { useLocalStorage } from '../../hooks/useLocalStorage'; +import { setSchedule } from '../../actions/profile'; +import { CustomSelect } from '../view/CustomSelect'; +import { CustomTimePicker } from '../view/CustomTimePicker'; +import { LinkButton } from '../view/LinkButton'; +import { OutlinedButton } from '../view/OutlinedButton'; + +type EditExpertScheduleModalProps = { + open: boolean; + handleCancel: () => void; + locale: string; + data?: ScheduleDTO; + refresh: () => void; +}; + +const DEFAULT_WORK: MapWorkingTime = { startDay: '' }; + +export const EditExpertScheduleModal: FC = ({ + open, + handleCancel, + locale, + data, + refresh, +}) => { + const defaultTimeZone = dayjs().format('Z'); + const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); + const [timeZone, setTimeZone] = useState(defaultTimeZone); + const [workList, setWorkList] = useState([DEFAULT_WORK]); + const [loading, setLoading] = useState(false); + + useEffect(() => { + if (open && data?.workingTimes && data.workingTimes.length > 0) { + setWorkList(formattedSchedule(data.workingTimes, timeZone)); + } + }, [open]); + + const onSave = () => { + const workingTimes = formattedWorkList(workList, timeZone); + + setLoading(true); + setSchedule(locale, jwt, { workingTimes }) + .then(() => { + handleCancel(); + refresh(); + }) + .catch(() => { + message.error('Не удалось сохранить расписание'); + }) + .finally(() => { + setLoading(false); + }) + }; + + const addWorkingHours = () => { + setWorkList([ + ...workList, + DEFAULT_WORK + ]); + }; + + const deleteWorkingHours = (index: number) => { + setWorkList(workList.filter((work, i) => i !== index)); + }; + + const onChangeWeekDay = (val: string, index: number) => { + setWorkList(workList.map((work, i) => { + if (i === index) { + return { + ...work, + startDay: val + } + } + + return work; + })); + }; + + const onChangeTime = (time: string, index: number, start?: boolean) => { + setWorkList(workList.map((work, i) => { + if (i === index) { + const timeMin = getNewTime(time); + let res; + + if (start) { + res = { + startTimeMin: timeMin + } + } else { + res = { + endTimeMin: timeMin + } + } + + return { + ...work, + ...res + } + } + + return work; + })); + }; + + const onChangeTimeZone = (newTimeZone: string) => { + const offset = getTimeZoneOffset(timeZone, newTimeZone); + setTimeZone(newTimeZone); + setWorkList(workList.map((work) => formattedTimeByOffset(work, offset))); + } + + return ( + } + > +
+
{i18nText('schedule', locale)}
+
+
+
+
+
+
{`${i18nText('yourTimezone', locale)}: ${defaultTimeZone}`}
+
+ ({ value, label: value }))} + onChange={(val) => onChangeTimeZone(val)} + /> +
+
+

{i18nText('workTime', locale)}

+
+ {workList.length === 1 ? workList.map(({ startDay, startTimeMin, endTimeMin }, index) => ( +
+ + + +
+ )) : null} + {workList.length > 1 ? workList.map(({ startDay, startTimeMin, endTimeMin }, index) => ( +
+ ({ value, label: i18nText(value, locale) }))} + onChange={(val) => onChangeWeekDay(val, index)} + /> + onChangeTime(timeString, index, true)} + /> + onChangeTime(timeString, index)} + /> + } + onClick={() => deleteWorkingHours(index)} + /> +
+ )) : null} +
+ + {i18nText('addWorkingHours', locale)} + +
+
+
+
+
+ +
+
+
+ ); +}; diff --git a/src/components/view/CustomMultiSelect.tsx b/src/components/view/CustomMultiSelect.tsx index 1ea80ef..32b92d3 100644 --- a/src/components/view/CustomMultiSelect.tsx +++ b/src/components/view/CustomMultiSelect.tsx @@ -30,7 +30,9 @@ export const CustomMultiSelect = (props: any) => { return (
-
{label}
+
+ {label} +
{ + const { label, value, ...other } = props; + const [isActiveLabel, setIsActiveLabel] = useState(false); + + useEffect(() => { + if (label) { + setIsActiveLabel(!!value); + } else { + setIsActiveLabel(false); + } + }, [value]); + + const onOpenChange = (open: boolean) => { + if (open) { + if (!isActiveLabel) setIsActiveLabel(true) + } else { + setIsActiveLabel(!!value) + } + }; + + return ( +
+
+ {label} +
+ } + {...other} + /> +
+ ); +}; diff --git a/src/constants/time.ts b/src/constants/time.ts new file mode 100644 index 0000000..f6819e4 --- /dev/null +++ b/src/constants/time.ts @@ -0,0 +1,37 @@ +export const UTC_LIST = [ + '-12:00', + '-11:00', + '-10:00', + '-09:30', + '-09:00', + '-08:00', + '-07:00', + '-06:00', + '-05:00', + '-04:00', + '-03:30', + '-03:00', + '-02:00', + '-01:00', + '+00:00', + '+01:00', + '+02:00', + '+03:00', + '+03:30', + '+04:00', + '+04:30', + '+05:00', + '+05:30', + '+06:00', + '+06:30', + '+07:00', + '+08:00', + '+09:00', + '+09:30', + '+10:00', + '+10:30', + '+11:00', + '+12:00', + '+13:00', + '+14:00' +]; diff --git a/src/i18nKeys/de.ts b/src/i18nKeys/de.ts index a12d82f..3a2f8c0 100644 --- a/src/i18nKeys/de.ts +++ b/src/i18nKeys/de.ts @@ -126,6 +126,7 @@ export default { workTime: 'Arbeitszeit', startAt: 'Beginn um', finishAt: 'Ende um', + day: 'Tag', addWorkingHours: 'Arbeitszeiten hinzufügen', specialisation: 'Spezialisierung', selectSpecialisation: 'Wählen Sie Ihre Spezialisierung, um fortzufahren', diff --git a/src/i18nKeys/en.ts b/src/i18nKeys/en.ts index 117a2b7..4be1bc4 100644 --- a/src/i18nKeys/en.ts +++ b/src/i18nKeys/en.ts @@ -126,6 +126,7 @@ export default { workTime: 'Work time', startAt: 'Start at', finishAt: 'Finish at', + day: 'Day', addWorkingHours: 'Add working hours', specialisation: 'Specialisation', selectSpecialisation: 'Select your specialisation to proceed', diff --git a/src/i18nKeys/es.ts b/src/i18nKeys/es.ts index 5f4a314..f869ff2 100644 --- a/src/i18nKeys/es.ts +++ b/src/i18nKeys/es.ts @@ -126,6 +126,7 @@ export default { workTime: 'Tiempo de trabajo', startAt: 'Empieza a las', finishAt: 'Termina a las', + day: 'Día', addWorkingHours: 'Añadir horas de trabajo', specialisation: 'Especialización', selectSpecialisation: 'Selecciona tu especialización para continuar', diff --git a/src/i18nKeys/fr.ts b/src/i18nKeys/fr.ts index 3502aef..6bad2ed 100644 --- a/src/i18nKeys/fr.ts +++ b/src/i18nKeys/fr.ts @@ -126,6 +126,7 @@ export default { workTime: 'Heures de travail', startAt: 'Commencer à', finishAt: 'Finir à', + day: 'Jour', addWorkingHours: 'Ajouter des heures de travail', specialisation: 'Spécialisation', selectSpecialisation: 'Sélectionnez votre spécialisation pour continuer', diff --git a/src/i18nKeys/it.ts b/src/i18nKeys/it.ts index 8a3743a..81a7d39 100644 --- a/src/i18nKeys/it.ts +++ b/src/i18nKeys/it.ts @@ -126,6 +126,7 @@ export default { workTime: 'Orario di lavoro', startAt: 'Inizia alle', finishAt: 'Termina alle', + day: 'Giorno', addWorkingHours: 'Aggiungi ore lavorative', specialisation: 'Specializzazione', selectSpecialisation: 'Seleziona la tua specializzazione per continuare', diff --git a/src/i18nKeys/ru.ts b/src/i18nKeys/ru.ts index de2a14b..b7db7b9 100644 --- a/src/i18nKeys/ru.ts +++ b/src/i18nKeys/ru.ts @@ -126,6 +126,7 @@ export default { workTime: 'Рабочее время', startAt: 'Начало в', finishAt: 'Завершение в', + day: 'День', addWorkingHours: 'Добавить рабочие часы', specialisation: 'Специализация', selectSpecialisation: 'Выберите свою специализацию для продолжения', diff --git a/src/styles/_pages.scss b/src/styles/_pages.scss index 394a403..eb8eafc 100644 --- a/src/styles/_pages.scss +++ b/src/styles/_pages.scss @@ -1395,6 +1395,7 @@ background-position: 99% 50%; background-repeat: no-repeat; padding: 16px; + border-radius: 8px; &__title { color: #FFBD00; @@ -1421,6 +1422,16 @@ background: #C4DFE6; } + &-item { + display: grid; + gap: 8px; + grid-template-columns: 80px 1fr 1fr 32px; + + &__single { + grid-template-columns: 80px 1fr 1fr; + } + } + &__inner{ display: flex; flex-direction: column; @@ -1437,6 +1448,7 @@ &__wrap { display: flex; gap: 8px; + flex-direction: column; .btn-cancel, .btn-edit { diff --git a/src/styles/view/_select.scss b/src/styles/view/_select.scss index e124a3c..a68558e 100644 --- a/src/styles/view/_select.scss +++ b/src/styles/view/_select.scss @@ -53,8 +53,15 @@ position: absolute; left: 16px; top: 15px; + right: 22px; z-index: 1; transition: all .1s ease; + overflow: hidden; + text-overflow: ellipsis; + + span { + white-space: nowrap; + } } } @@ -110,7 +117,14 @@ position: absolute; left: 16px; top: 15px; + right: 22px; z-index: 1; transition: all .1s ease; + overflow: hidden; + text-overflow: ellipsis; + + span { + white-space: nowrap; + } } } diff --git a/src/styles/view/_timepicker.scss b/src/styles/view/_timepicker.scss new file mode 100644 index 0000000..6d5cda3 --- /dev/null +++ b/src/styles/view/_timepicker.scss @@ -0,0 +1,59 @@ +.b-timepicker { + width: 100% !important; + height: 54px !important; + + &.ant-picker-filled { + background: transparent !important; + z-index: 1; + padding-top: 22px !important; + + &:hover { + border-color: #2c7873 !important; + } + + .ant-picker-input { + input { + font-size: 15px !important; + } + } + } + + .ant-picker-suffix { + margin-top: -20px; + } + + &-wrap { + position: relative; + width: 100%; + background-color: #F8F8F7; + border-radius: 8px; + + &.b-timepicker__active .b-timepicker-label { + font-size: 12px; + font-weight: 300; + line-height: 14px; + top: 8px; + } + } + + &-label { + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 24px; + color: #000; + opacity: .3; + position: absolute; + left: 16px; + top: 15px; + right: 22px; + z-index: 0; + transition: all .1s ease; + overflow: hidden; + text-overflow: ellipsis; + + span { + white-space: nowrap; + } + } +} diff --git a/src/styles/view/style.scss b/src/styles/view/style.scss index 42b0936..b626bbd 100644 --- a/src/styles/view/style.scss +++ b/src/styles/view/style.scss @@ -8,3 +8,4 @@ @import "_buttons.scss"; @import "_practice.scss"; @import "_collapse.scss"; +@import "_timepicker.scss"; diff --git a/src/types/profile.ts b/src/types/profile.ts index 6da1047..3d30c4e 100644 --- a/src/types/profile.ts +++ b/src/types/profile.ts @@ -1,8 +1,8 @@ import { UploadFile } from 'antd'; -import {EducationDTO} from "./education"; -import {ExpertsTags} from "./tags"; -import {PracticeDTO} from "./practice"; -import {ScheduleDTO} from "./schedule"; +import { EducationDTO } from './education'; +import { ExpertsTags } from './tags'; +import { PracticeDTO } from './practice'; +import { ScheduleDTO } from './schedule'; export type ProfileData = { username?: string; diff --git a/src/types/schedule.ts b/src/types/schedule.ts index fc18ca9..4e56926 100644 --- a/src/types/schedule.ts +++ b/src/types/schedule.ts @@ -3,8 +3,15 @@ export type WorkingTime = { startTimeUtc?: number, endDayOfWeekUtc?: string, endTimeUtc?: number -} +}; export interface ScheduleDTO { workingTimes?: WorkingTime[] } + +export type MapWorkingTime = { + startDay?: string; + startTimeMin?: number; + endDay?: string; + endTimeMin?: number; +}; diff --git a/src/utils/time.ts b/src/utils/time.ts index 9657e3a..86f0cf9 100644 --- a/src/utils/time.ts +++ b/src/utils/time.ts @@ -1,16 +1,24 @@ -import dayjs from 'dayjs'; -import { WorkingTime } from '../types/schedule'; +import { MapWorkingTime, WorkingTime } from '../types/schedule'; -const WEEK_DAY = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; +export const WEEK_DAY = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; const MAX_DAY_TIME = 24 * 60; // min -export const getCurrentTime = (data: WorkingTime) => { +const addWeekDay = (weekDay?: string): string => { + const ind = weekDay ? WEEK_DAY.indexOf(weekDay) + 1 : 0; + return WEEK_DAY[ind >= WEEK_DAY.length ? 0 : ind]; +} + +const subWeekDay = (weekDay?: string): string => { + const ind = weekDay ? WEEK_DAY.indexOf(weekDay) - 1 : 0; + return WEEK_DAY[ind < 0 ? WEEK_DAY.length - 1 : ind]; +} + +export const getCurrentTime = (data: WorkingTime, timeZone: string): MapWorkingTime => { let startDay = data.startDayOfWeekUtc; let endDay = data.endDayOfWeekUtc; - const currentTimeZone = dayjs().format('Z'); const startUtc = data.startTimeUtc / (1000 * 1000 * 60); const endUtc = data.endTimeUtc / (1000 * 1000 * 60); - const matches = currentTimeZone.match(/(\+|-)([0-9]{2}):([0-9]{2})/); + const matches = timeZone.match(/(\+|-)([0-9]{2}):([0-9]{2})/) || []; const sign = matches[1]; const h = matches[2]; const m = matches[3]; @@ -29,34 +37,186 @@ export const getCurrentTime = (data: WorkingTime) => { endMin = endUtc - (Number(h) * 60) - Number(m); } - if (startMin > MAX_DAY_TIME) { - startMin = startMin - MAX_DAY_TIME; - const ind = startDay ? WEEK_DAY.indexOf(startDay) + 1 : 0; - startDay = WEEK_DAY[ind >= WEEK_DAY.length ? 0 : ind]; + if (startMin >= MAX_DAY_TIME) { + startMin = 0; + // startMin = startMin - MAX_DAY_TIME; + // const ind = startDay ? WEEK_DAY.indexOf(startDay) + 1 : 0; + // startDay = WEEK_DAY[ind >= WEEK_DAY.length ? 0 : ind]; } - if (endMin > MAX_DAY_TIME) { - endMin = endMin - MAX_DAY_TIME; - const ind = endDay ? WEEK_DAY.indexOf(endDay) + 1 : 0; - endDay = WEEK_DAY[ind >= WEEK_DAY.length ? 0 : ind]; + if (endMin >= MAX_DAY_TIME) { + endMin = 0; + // endMin = endMin - MAX_DAY_TIME; + // const ind = endDay ? WEEK_DAY.indexOf(endDay) + 1 : 0; + // endDay = WEEK_DAY[ind >= WEEK_DAY.length ? 0 : ind]; } if (startMin <= 0) { - startMin = MAX_DAY_TIME - startMin; - const ind = startDay ? WEEK_DAY.indexOf(startDay) - 1 : 0; - startDay = WEEK_DAY[ind < 0 ? WEEK_DAY.length - 1 : ind]; + startMin = 0; + // startMin = MAX_DAY_TIME - startMin; + // const ind = startDay ? WEEK_DAY.indexOf(startDay) - 1 : 0; + // startDay = WEEK_DAY[ind < 0 ? WEEK_DAY.length - 1 : ind]; } if (endMin <= 0) { - endMin = MAX_DAY_TIME - endMin; - const ind = endDay ? WEEK_DAY.indexOf(endDay) - 1 : 0; - endDay = WEEK_DAY[ind < 0 ? WEEK_DAY.length - 1 : ind]; + endMin = 0; + // endMin = MAX_DAY_TIME - endMin; + // const ind = endDay ? WEEK_DAY.indexOf(endDay) - 1 : 0; + // endDay = WEEK_DAY[ind < 0 ? WEEK_DAY.length - 1 : ind]; } return { startDay, - startTime: `${(startMin - startMin % 60)/60}:${startMin % 60 || '00'}`, + startTimeMin: startMin, endDay: endDay, - endTime: `${(endMin - endMin % 60)/60}:${endMin % 60 || '00'}` + endTimeMin: endMin } }; + +export const getTimeString = (min: number) => { + const timeH = `${(min - min % 60)/60}`; + + return `${timeH.length === 1 ? `0${timeH}` : timeH}:${min % 60 || '00'}`; +} + +export const formattedSchedule = (workingTimes: WorkingTime[], timeZone: string): MapWorkingTime[] => ( + workingTimes.map((time) => getCurrentTime(time, timeZone)) || [] +); + +export const getNewTime = (time: string): number => { + const timeArr = time.split(':'); + + return Number(timeArr[0]) * 60 + Number(timeArr[1]); +}; + +export const getTimeZoneOffset = (oldTime: string, newTime: string): { sign: string, offset: number } => { + // старая таймзона + const matches1 = oldTime.match(/(\+|-)([0-9]{2}):([0-9]{2})/) || []; + const sign1 = matches1[1]; + const min1 = Number(matches1[2]) * 60 + Number(matches1[3]); + // новая таймзона + const matches2 = newTime.match(/(\+|-)([0-9]{2}):([0-9]{2})/) || []; + const sign2 = matches2[1]; + const min2 = Number(matches2[2]) * 60 + Number(matches2[3]); + + if (sign1 === '+' && sign2 === '+') { + if (min1 < min2) { + return { + sign: '+', + offset: min2 - min1 + } + } else { + return { + sign: '-', + offset: min1 - min2 + } + } + } + + if (sign1 === '-' && sign2 === '-') { + if (min1 < min2) { + return { + sign: '-', + offset: min2 - min1 + } + } else { + return { + sign: '+', + offset: min1 - min2 + } + } + } + + if (sign1 === '+' && sign2 === '-') { + return { + sign: '-', + offset: min1 + min2 + } + } + + if (sign1 === '-' && sign2 === '+') { + return { + sign: '+', + offset: min1 + min2 + } + } +} + +export const formattedTimeByOffset = (workTime: MapWorkingTime, objOffset: { sign: string, offset: number }): MapWorkingTime => { + if (objOffset.sign === '+') { + const resStartMin = workTime.startTimeMin + objOffset.offset; + const resEndMin = workTime.endTimeMin ? workTime.endTimeMin + objOffset.offset : workTime.endTimeMin; + + return { + ...workTime, + startTimeMin: resStartMin >= MAX_DAY_TIME ? 0 : resStartMin, + endTimeMin: resEndMin >= MAX_DAY_TIME ? 0 : resEndMin + } + } + + if (objOffset.sign === '-') { + const resStartMin = workTime.startTimeMin - objOffset.offset; + const resEndMin = workTime.endTimeMin ? workTime.endTimeMin - objOffset.offset : workTime.endTimeMin; + + return { + ...workTime, + startTimeMin: resStartMin < 0 ? 0 : resStartMin, + endTimeMin: resEndMin < 0 ? 0 : resEndMin + } + } + + return workTime; +}; + +const getResultTime = (data: MapWorkingTime, timeZone: string): WorkingTime => { + let startDayOfWeekUtc = data.startDay; + let endDayOfWeekUtc = data.startDay; + const matches = timeZone.match(/(\+|-)([0-9]{2}):([0-9]{2})/) || []; + const sign = matches[1]; + const offset = (Number(matches[2]) * 60) + Number(matches[3]); + let startTime = data.startTimeMin; + let endTime = data.endTimeMin === 0 ? MAX_DAY_TIME : data.endTimeMin; + + if (sign === '+') { + startTime = startTime - offset; + endTime = endTime - offset; + } + + if (sign === '-') { + startTime = startTime + offset; + endTime = endTime + offset; + } + + if (startTime >= MAX_DAY_TIME) { + startTime = startTime - MAX_DAY_TIME; + startDayOfWeekUtc = addWeekDay(startDayOfWeekUtc); + } + + if (endTime >= MAX_DAY_TIME) { + endTime = endTime - MAX_DAY_TIME; + endDayOfWeekUtc = addWeekDay(endDayOfWeekUtc); + } + + if (startTime < 0) { + startTime = MAX_DAY_TIME - Math.abs(startTime || 0); + startDayOfWeekUtc = subWeekDay(startDayOfWeekUtc); + } + + if (endTime < 0) { + endTime = MAX_DAY_TIME - Math.abs(endTime || 0); + endDayOfWeekUtc = subWeekDay(endDayOfWeekUtc); + } + + return { + startDayOfWeekUtc, + startTimeUtc: startTime * 1000 * 1000 * 60, + endDayOfWeekUtc, + endTimeUtc: endTime * 1000 * 1000 * 60 + }; +} + +export const formattedWorkList = (workingList: MapWorkingTime[], timeZone: string): WorkingTime[] => ( + workingList + .filter(({ startTimeMin, endTimeMin }) => !(!startTimeMin && !endTimeMin)) + .map((time) => getResultTime(time, timeZone)) || [] +);