feat: add edit modal for expert's schedule

This commit is contained in:
SD 2024-09-05 20:06:00 +04:00
parent cda91b9ea9
commit b52096b3bc
24 changed files with 637 additions and 68 deletions

View File

@ -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 (
<Loader isLoading={loading}>
{data && (
<ExpertProfile
isFull={isFull}
locale={locale}
data={data}
updateData={setData}
/>
)}
</Loader>
);
};

View File

@ -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) {

View File

@ -1,4 +1,4 @@
import {fetchBlogPosts} from "../lib/contentful/blogPosts";
import { fetchBlogPosts } from '../lib/contentful/blogPosts';
export default async function sitemap() {
const paths = [

View File

@ -145,7 +145,13 @@ export const ExpertProfile = ({ locale, data, updateData, isFull }: ExpertProfil
updateExpert={updateExpert}
/>
</Loader>
<ExpertSchedule locale={locale} data={data?.schedule} />
<Loader isLoading={loading.includes('schedule')}>
<ExpertSchedule
locale={locale}
data={data?.schedule}
updateExpert={updateExpert}
/>
</Loader>
<Loader isLoading={loading.includes('education')}>
<ExpertEducation
locale={locale}

View File

@ -1,14 +1,14 @@
'use client'
import {useState} from "react";
import {Tag} from "antd";
import {EditOutlined} from "@ant-design/icons";
import {LinkButton} from "../../view/LinkButton";
import {ExpertData, ProfileData} from "../../../types/profile";
import {i18nText} from "../../../i18nKeys/index";
import {PracticeDTO} from "../../../types/practice";
import {ExpertPractice} from "../../Experts/ExpertDetails";
import {EditExpertAboutModal} from "../../Modals/EditExpertAboutModal";
import { useState } from 'react';
import { Tag } from 'antd';
import { EditOutlined } from '@ant-design/icons';
import { ExpertData, ProfileData } from '../../../types/profile';
import { i18nText } from '../../../i18nKeys';
import { PracticeDTO } from '../../../types/practice';
import { LinkButton } from '../../view/LinkButton';
import { ExpertPractice } from '../../Experts/ExpertDetails';
import { EditExpertAboutModal } from '../../Modals/EditExpertAboutModal';
type ExpertAboutProps = {
locale: string;

View File

@ -1,48 +1,56 @@
import { useState } from 'react';
import { Tag } from 'antd';
import { EditOutlined } from '@ant-design/icons';
import dayjs from 'dayjs';
import { ScheduleDTO } from '../../../types/schedule';
import { i18nText } from '../../../i18nKeys';
import { getCurrentTime, getTimeString } from '../../../utils/time';
import { ExpertData } from '../../../types/profile';
import { LinkButton } from '../../view/LinkButton';
import {useState} from "react";
import {Tag} from "antd";
import {getCurrentTime} from "../../../utils/time";
import { EditExpertScheduleModal } from '../../Modals/EditExpertScheduleModal';
type ExpertScheduleProps = {
locale: string;
data?: ScheduleDTO;
updateExpert: (key: keyof ExpertData) => void;
};
export const ExpertSchedule = ({ locale, data }: ExpertScheduleProps) => {
export const ExpertSchedule = ({ locale, data, updateExpert }: ExpertScheduleProps) => {
const [showEdit, setShowEdit] = useState<boolean>(false);
// person51
return (
<div className="coaching-section__wrap">
<div className="coaching-section">
<div className="coaching-section__title">
<h2 className="title-h2">{i18nText('schedule', locale)}</h2>
{/*<LinkButton
<LinkButton
type="link"
icon={<EditOutlined />}
onClick={() => setShowEdit(true)}
/>*/}
/>
</div>
<div className="b-schedule-list">
{data && data?.workingTimes?.map((date, index) => {
const { startDay, startTime, endDay, endTime } = getCurrentTime(date);
const { startDay, startTimeMin, endTimeMin } = getCurrentTime(date, dayjs().format('Z'));
return (
<div key={`date_${index}`}>
<Tag className="skills__list__item">{i18nText(startDay, locale)}</Tag>
<div>{startTime}</div>
<div>{startTimeMin ? getTimeString(startTimeMin) : '00:00'}</div>
<span>-</span>
{startDay !== endDay && <Tag className="skills__list__item">{i18nText(endDay, locale)}</Tag>}
<div>{endTime}</div>
<div>{endTimeMin ? getTimeString(endTimeMin) : '00:00'}</div>
</div>
)
})}
</div>
</div>
<EditExpertScheduleModal
open={showEdit}
handleCancel={() => setShowEdit(false)}
locale={locale}
data={data}
refresh={() => updateExpert('schedule')}
/>
</div>
);
};

View File

@ -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<EditExpertScheduleModalProps> = ({
open,
handleCancel,
locale,
data,
refresh,
}) => {
const defaultTimeZone = dayjs().format('Z');
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
const [timeZone, setTimeZone] = useState<string>(defaultTimeZone);
const [workList, setWorkList] = useState<MapWorkingTime[]>([DEFAULT_WORK]);
const [loading, setLoading] = useState<boolean>(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 (
<Modal
className="b-modal"
open={open}
title={undefined}
onOk={undefined}
onCancel={handleCancel}
footer={false}
width={498}
closeIcon={<CloseOutlined style={{ fontSize: 20, color: '#000' }}/>}
>
<div className="b-modal__expert__content">
<div className="b-modal__expert__title">{i18nText('schedule', locale)}</div>
<div className="b-modal__expert__inner" style={{ paddingRight: 12 }}>
<div style={{ paddingRight: 0, paddingBottom: 1 }}>
<div className="schedule">
<div className="schedule__inner">
<div className="timezone">
<div className="timezone__title">{`${i18nText('yourTimezone', locale)}: ${defaultTimeZone}`}</div>
<div className="timezone__utc">
<CustomSelect
label="UTC"
value={timeZone}
options={UTC_LIST.map((value) => ({ value, label: value }))}
onChange={(val) => onChangeTimeZone(val)}
/>
</div>
</div>
<h3 className="title-h3">{i18nText('workTime', locale)}</h3>
<div className="schedule__wrap">
{workList.length === 1 ? workList.map(({ startDay, startTimeMin, endTimeMin }, index) => (
<div key={`day_${index}`} className="schedule-item__single">
<CustomSelect />
<CustomSelect label={i18nText('startAt', locale)} />
<CustomSelect label={i18nText('finishAt', locale)} />
</div>
)) : null}
{workList.length > 1 ? workList.map(({ startDay, startTimeMin, endTimeMin }, index) => (
<div key={`day_${index}`} className="schedule-item">
<CustomSelect
label={i18nText('day', locale)}
value={startDay || undefined}
options={WEEK_DAY.map((value) => ({ value, label: i18nText(value, locale) }))}
onChange={(val) => onChangeWeekDay(val, index)}
/>
<CustomTimePicker
label={i18nText('startAt', locale)}
value={startTimeMin ? dayjs(getTimeString(startTimeMin), 'HH:mm') : dayjs('00:00', 'HH:mm')}
onChange={(time, timeString) => onChangeTime(timeString, index, true)}
/>
<CustomTimePicker
label={i18nText('finishAt', locale)}
value={endTimeMin ? dayjs(getTimeString(endTimeMin), 'HH:mm') : dayjs('00:00', 'HH:mm')}
onChange={(time, timeString) => onChangeTime(timeString, index)}
/>
<LinkButton
type="link"
danger
icon={<DeleteOutlined />}
onClick={() => deleteWorkingHours(index)}
/>
</div>
)) : null}
</div>
<OutlinedButton
type="link"
onClick={addWorkingHours}
>
{i18nText('addWorkingHours', locale)}
</OutlinedButton>
</div>
</div>
</div>
</div>
<div className="b-modal__expert__button">
<Button
className="card-detail__apply"
onClick={onSave}
loading={loading}
>
{i18nText('save', locale)}
</Button>
</div>
</div>
</Modal>
);
};

View File

@ -30,7 +30,9 @@ export const CustomMultiSelect = (props: any) => {
return (
<div className={`b-multiselect-wrap ${isActiveLabel ? 'b-multiselect__active' : ''}`}>
<div className="b-multiselect-label">{label}</div>
<div className="b-multiselect-label">
<span>{label}</span>
</div>
<Select
className="b-multiselect"
mode="multiple"

View File

@ -4,7 +4,7 @@ import React, { useEffect, useState } from 'react';
import { Select } from 'antd';
export const CustomSelect = (props: any) => {
const { label, value, ...other } = props;
const { label, value, style, ...other } = props;
const [isActiveLabel, setIsActiveLabel] = useState<boolean>(false);
useEffect(() => {
@ -16,8 +16,10 @@ export const CustomSelect = (props: any) => {
}, [value]);
return (
<div className={`b-select-wrap ${isActiveLabel ? 'b-select__active' : ''}`}>
<div className="b-select-label">{label}</div>
<div className={`b-select-wrap ${isActiveLabel ? 'b-select__active' : ''}`} style={style}>
<div className="b-select-label">
<span>{label}</span>
</div>
<Select
className="b-select"
value={value}

View File

@ -0,0 +1,48 @@
'use client'
import React, { useEffect, useState } from 'react';
import { TimePicker } from 'antd';
import { DownOutlined } from '@ant-design/icons';
export const CustomTimePicker = (props: any) => {
const { label, value, ...other } = props;
const [isActiveLabel, setIsActiveLabel] = useState<boolean>(false);
useEffect(() => {
if (label) {
setIsActiveLabel(!!value);
} else {
setIsActiveLabel(false);
}
}, [value]);
const onOpenChange = (open: boolean) => {
if (open) {
if (!isActiveLabel) setIsActiveLabel(true)
} else {
setIsActiveLabel(!!value)
}
};
return (
<div className={`b-timepicker-wrap ${isActiveLabel ? 'b-timepicker__active' : ''}`}>
<div className="b-timepicker-label">
<span>{label}</span>
</div>
<TimePicker
className="b-timepicker"
format="HH:mm"
minuteStep={15}
value={value}
showNow={false}
onOpenChange={onOpenChange}
needConfirm={false}
placeholder=""
variant="filled"
allowClear={false}
suffixIcon={<DownOutlined style={{ color: '#2c7873', fontSize: 12 }} />}
{...other}
/>
</div>
);
};

37
src/constants/time.ts Normal file
View File

@ -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'
];

View File

@ -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',

View File

@ -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',

View File

@ -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',

View File

@ -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',

View File

@ -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',

View File

@ -126,6 +126,7 @@ export default {
workTime: 'Рабочее время',
startAt: 'Начало в',
finishAt: 'Завершение в',
day: 'День',
addWorkingHours: 'Добавить рабочие часы',
specialisation: 'Специализация',
selectSpecialisation: 'Выберите свою специализацию для продолжения',

View File

@ -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 {

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -8,3 +8,4 @@
@import "_buttons.scss";
@import "_practice.scss";
@import "_collapse.scss";
@import "_timepicker.scss";

View File

@ -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;

View File

@ -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;
};

View File

@ -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)) || []
);