bbuddy-ui/src/components/Modals/ScheduleModal.tsx

287 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import React, {FC, useEffect, useState} from 'react';
import classNames from 'classnames';
import { Modal, Menu, Calendar, Radio, Button, Input, message, Form } from 'antd';
import type { CalendarProps, MenuProps } from 'antd';
import { ArrowLeftOutlined } from '@ant-design/icons';
import { CloseOutlined } from '@ant-design/icons';
import dayjs, { Dayjs } from 'dayjs';
import 'dayjs/locale/ru';
import 'dayjs/locale/en';
import 'dayjs/locale/de';
import 'dayjs/locale/it';
import 'dayjs/locale/fr';
import 'dayjs/locale/es';
import { getLocale } from '../../utils/locale';
import { AUTH_TOKEN_KEY, SESSION_DATA } from '../../constants/common';
import { ExpertScheduler, SignupSessionData } from '../../types/experts';
import { Tag } from '../../types/tags';
import { getSchedulerByExpertId, getSchedulerSession } from '../../actions/experts';
import { StripeElementsForm } from '../stripe/StripeElementsForm';
import { i18nText } from '../../i18nKeys';
import { CustomSelect } from '../../components/view/CustomSelect';
import { Loader } from '../view/Loader';
import { getStorageValue } from '../../hooks/useLocalStorage';
type ScheduleModalProps = {
open: boolean;
handleCancel: () => void;
mode: 'data' | 'time' | 'pay' | 'finish';
updateMode: (mode: 'data' | 'time' | 'pay' | 'finish') => void;
sessionCost: number;
expertId: string;
locale: string;
checkSession: (data?: SignupSessionData) => void;
};
type MenuItem = Required<MenuProps>['items'][number];
const getCalendarMenu = (start: Dayjs): MenuItem[] => Array.from({ length: 3 })
.map((_: unknown, index: number) => {
const date = index ? start.add(index, 'M') : start.clone();
return {
label: <span className="b-calendar-month">{date.format('MMMM')}</span>,
key: date.format('YYYY-MM-DD')
}
});
export const ScheduleModal: FC<ScheduleModalProps> = ({
open,
handleCancel,
mode,
updateMode,
sessionCost,
locale,
expertId,
checkSession,
}) => {
const [selectDate, setSelectDate] = useState<Dayjs>(dayjs());
const [dates, setDates] = useState<Record<string, { startTime: string, endTime: string }[]> | undefined>();
const [tags, setTags] = useState<Tag[] | undefined>();
const [rawScheduler, setRawScheduler] = useState<ExpertScheduler | null>(null);
const [isPayLoading, setIsPayLoading] = useState<boolean>(false);
const [sessionId, setSessionId] = useState<string>('');
const [form] = Form.useForm<{ clientComment?: string, startAtUtc?: string, tagId?: number }>();
dayjs.locale(locale);
const signupSession = () => {
const data = sessionStorage?.getItem(SESSION_DATA);
const jwt = getStorageValue(AUTH_TOKEN_KEY, '');
if (jwt && data) {
const parseData = JSON.parse(data);
setIsPayLoading(true);
getSchedulerSession(parseData as SignupSessionData, locale || 'en', jwt)
.then((session) => {
setSessionId(session?.sessionId);
console.log(session?.sessionId);
})
.catch((err) => {
console.log(err);
message.error('Не удалось провести оплату')
})
.finally(() => {
sessionStorage?.removeItem(SESSION_DATA);
setIsPayLoading(false);
})
}
};
useEffect(()=> {
if (open && mode !== 'pay') {
getSchedulerByExpertId(expertId as string, locale as string)
.then((data) => {
setRawScheduler(data);
})
.catch((err) => {
console.log(err);
});
}
if (!open) {
form.resetFields();
}
}, [open]);
useEffect(() => {
if (open && mode === 'pay') {
signupSession();
}
}, [mode]);
useEffect(() => {
const map = {} as any
rawScheduler?.availableSlots.forEach((el) => {
const key = dayjs(el.startTime).format('YYYY-MM-DD');
if (!map[key]){
map[key] = []
}
map[key].push(el);
})
setDates(map);
setTags(rawScheduler?.tags)
}, [rawScheduler]);
const onPanelChange = (value: Dayjs) => setSelectDate(value);
const onDateChange: CalendarProps<Dayjs>['onSelect'] = (value, selectInfo) => {
if (selectInfo.source === 'date') {
setSelectDate(value);
updateMode('time');
}
};
const disabledDate = (currentDate: Dayjs) => !dates || !dates[currentDate.format('YYYY-MM-DD')];
const cellRender: CalendarProps<Dayjs>['fullCellRender'] = (date, info) => {
const isWeekend = date.day() === 6 || date.day() === 0;
return React.cloneElement(info.originNode, {
...info.originNode.props,
className: classNames('b-calendar-cell', {
['b-calendar-cell__select']: selectDate.isSame(date, 'date'),
['b-calendar-cell__today']: date.isSame(dayjs(), 'date'),
['b-calendar-cell__weekend']: isWeekend,
}),
children: (
<div>
<span>
{date.get('date')}
</span>
</div>
),
});
};
const onValidate = () => {
form.validateFields()
.then((values) => {
checkSession({ coachId: +expertId, ...values });
})
}
return (
<Modal
className="b-modal"
open={open}
title={undefined}
onOk={undefined}
onCancel={handleCancel}
footer={false}
width={498}
closeIcon={<CloseOutlined style={{ fontSize: 20, color: '#000' }}/>}
>
{mode === 'data' && (
<Calendar
className="b-calendar"
fullscreen={false}
onPanelChange={onPanelChange}
fullCellRender={cellRender}
onSelect={onDateChange}
value={selectDate}
disabledDate={disabledDate}
locale={getLocale(locale)}
validRange={[selectDate.startOf('M'), selectDate.endOf('M')]}
headerRender={({ onChange }) => {
const start = dayjs().startOf('M');
const [activeMonth, setActiveMonth] = useState<string>(start.format('YYYY-MM-DD'));
const onClick: MenuProps['onClick'] = (e) => {
setActiveMonth(e.key);
onChange(dayjs(e.key));
};
return (
<Menu
className="b-calendar-header"
onClick={onClick}
selectedKeys={[activeMonth]}
mode="horizontal"
items={getCalendarMenu(start)}
/>
);
}}
/>
)}
{mode === 'time' && (
<div className="b-schedule-time">
<div className="b-schedule-time-header">
<Button
className="b-button-link-big"
type="link"
onClick={() => updateMode('data')}
icon={<ArrowLeftOutlined />}
iconPosition="start"
>
{selectDate.locale(locale).format('DD MMMM YYYY')}
</Button>
</div>
<Form form={form}>
<div className="b-schedule-select-tag">
{tags && (
<Form.Item
name="tagId"
rules={[{
required: true,
message: ''
}]}
>
<CustomSelect
label={i18nText('selectTopic', locale)}
options={tags?.map(({id, name}) => ({value: id, label: name}))}
/>
</Form.Item>
)}
</div>
<div className="b-schedule-radio-list">
<Form.Item
name="startAtUtc"
rules={[{
required: true,
message: ''
}]}
>
<Radio.Group>
{dates && dates[selectDate.format('YYYY-MM-DD')].map((el: any) => (
<Radio
key={el.startTime}
value={el.startTime}
>
{dayjs(el.startTime).format('HH:mm')} - {dayjs(el.endTime).format('HH:mm')}
</Radio>)
)}
</Radio.Group>
</Form.Item>
</div>
<Form.Item name="clientComment">
<Input.TextArea
className="b-textarea"
rows={2}
placeholder={i18nText('sessionWishes', locale)}
/>
</Form.Item>
</Form>
<Button
className="btn-apply"
onClick={onValidate}
>
{i18nText('pay', locale)}
</Button>
</div>
)}
{mode === 'pay' && (
<div className="b-schedule-payment">
<Loader isLoading={isPayLoading}>
<StripeElementsForm
amount={sessionCost}
locale={locale}
sessionId={sessionId}
/>
</Loader>
</div>
)}
</Modal>
);
};