feat: add edit modal for expert's schedule
This commit is contained in:
parent
cda91b9ea9
commit
b52096b3bc
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {fetchBlogPosts} from "../lib/contentful/blogPosts";
|
||||
import { fetchBlogPosts } from '../lib/contentful/blogPosts';
|
||||
|
||||
export default async function sitemap() {
|
||||
const paths = [
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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"
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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'
|
||||
];
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -126,6 +126,7 @@ export default {
|
|||
workTime: 'Рабочее время',
|
||||
startAt: 'Начало в',
|
||||
finishAt: 'Завершение в',
|
||||
day: 'День',
|
||||
addWorkingHours: 'Добавить рабочие часы',
|
||||
specialisation: 'Специализация',
|
||||
selectSpecialisation: 'Выберите свою специализацию для продолжения',
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,3 +8,4 @@
|
|||
@import "_buttons.scss";
|
||||
@import "_practice.scss";
|
||||
@import "_collapse.scss";
|
||||
@import "_timepicker.scss";
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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)) || []
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue