diff --git a/src/components/appointment/AppointmentCard.tsx b/src/components/appointment/AppointmentCard.tsx index a26b58759..04a9a22da 100644 --- a/src/components/appointment/AppointmentCard.tsx +++ b/src/components/appointment/AppointmentCard.tsx @@ -1,7 +1,7 @@ -import { gql, useApolloClient, useQuery } from '@apollo/client' +import { gql, useApolloClient, useMutation, useQuery } from '@apollo/client' import { Icon } from '@chakra-ui/icons' -import { SkeletonCircle, SkeletonText, Spinner, Textarea } from '@chakra-ui/react' -import { Button, Dropdown, Form, Icon as AntdIcon, Menu, message, Modal } from 'antd' +import { Button, ButtonGroup, SkeletonCircle, SkeletonText, Spinner, Textarea } from '@chakra-ui/react' +import { Divider, Dropdown, Form, Icon as AntdIcon, Menu, message, Modal } from 'antd' import { FormComponentProps } from 'antd/lib/form' import axios from 'axios' import BraftEditor from 'braft-editor' @@ -15,12 +15,14 @@ import { useIntl } from 'react-intl' import styled from 'styled-components' import hasura from '../../hasura' import { dateRangeFormatter } from '../../helpers' -import { useCancelAppointment, useUpdateAppointmentIssue } from '../../hooks/appointment' +import { useAppointmentPlan, useCancelAppointment, useUpdateAppointmentIssue } from '../../hooks/appointment' import DefaultAvatar from '../../images/avatar.svg' import { ReactComponent as CalendarOIcon } from '../../images/calendar-alt-o.svg' import { ReactComponent as UserOIcon } from '../../images/user-o.svg' +import { AppointmentPlan } from '../../types/appointment' import { CustomRatioImage } from '../common/Image' import { BREAK_POINT } from '../common/Responsive' +import AppointmentItem from './AppointmentItem' import appointmentMessages from './translation' const StyledCard = styled.div` @@ -73,12 +75,7 @@ const StyledCanceledText = styled.span` color: var(--gray-dark); font-size: 14px; ` -const StyledModal = styled(Modal)` - && .ant-modal-footer { - border-top: 0; - padding: 0 1.5rem 1.5rem; - } -` + const StyledModalTitle = styled.div` ${CommonTitleMixin} ` @@ -100,28 +97,119 @@ const StyledLabel = styled.div` letter-spacing: 0.4px; ` +const StyledScheduleTitle = styled.h3` + margin-bottom: 1.25rem; + display: block; + font-size: 16px; + font-weight: bold; + letter-spacing: 0.2px; + color: var(--gray-darker); +` + +const StyledMeta = styled.div` + color: var(--gray-dark); + font-size: 12px; +` + +const CustomMenu = styled(Menu)` + && .ant-dropdown-menu-item:hover, + .ant-dropdown-menu-submenu-title:hover { + color: #ffffff !important; + background-color: 'primary' !important; + } +` + type AppointmentCardProps = FormComponentProps & { orderProductId: string appointmentPlanId: string + memberId: string onRefetch?: () => void loadingCreator?: boolean } -const AppointmentCard: React.VFC = ({ orderProductId, appointmentPlanId, onRefetch, form }) => { +type AppointmentCardCreatorBlockProps = { + creator: { + avatarUrl: string | null + name: string | null + } + appointmentPlan: AppointmentPlan | null + loadingAppointmentPlan: boolean +} + +const AppointmentCardCreatorBlock: React.FC = ({ + creator, + appointmentPlan, + loadingAppointmentPlan, +}) => { + const { formatMessage } = useIntl() + + return ( + <> +
+
+ +
+
+ {creator.name} + + {formatMessage(appointmentMessages.AppointmentCard.periodDurationAtMost, { + duration: appointmentPlan?.duration, + })} + +
+
+ {!loadingAppointmentPlan && ( + + {formatMessage(appointmentMessages.AppointmentCard.rescheduleAppointmentPlanTitle, { + title: appointmentPlan?.title, + })} + + )} + + + ) +} + +const AppointmentCard: React.VFC = ({ + orderProductId, + appointmentPlanId, + memberId, + onRefetch, + form, +}) => { const { id: appId, enabledModules } = useApp() const { formatMessage } = useIntl() const { authToken, currentMemberId } = useAuth() const apolloClient = useApolloClient() const [issueModalVisible, setIssueModalVisible] = useState(false) const [cancelModalVisible, setCancelModalVisible] = useState(false) + const [rescheduleModalVisible, setRescheduleModalVisible] = useState(false) const [canceledReason, setCanceledReason] = useState('') + const [rescheduleAppointment, setRescheduleAppointment] = useState<{ + rescheduleAppointment: boolean + periodStartedAt: Date | null + periodEndedAt: Date | null + appointmentPlanId: string + }>() const [loading, setLoading] = useState(false) - const { loading: loadingOrderProduct, orderProduct } = useOrderProduct(orderProductId) + const { loading: loadingOrderProduct, orderProduct, refetchOrderProduct } = useOrderProduct(orderProductId) const { loading: loadingAppointmentPlanPreview, appointmentPlanPreview } = useAppointmentPlanPreview(appointmentPlanId) + const { loadingAppointmentPlan, appointmentPlan, refetchAppointmentPlan } = useAppointmentPlan( + appointmentPlanId, + memberId || '', + ) const { loading: loadingCreator, creator } = useCreator(appointmentPlanPreview.creatorId) const updateAppointmentIssue = useUpdateAppointmentIssue(orderProductId, orderProduct.options) const cancelAppointment = useCancelAppointment(orderProductId, orderProduct.options) + const updateAppointmentPeriod = useUpdateAppointmentPeriod(orderProductId, orderProduct.options) + const [confirm, setConfirm] = useState(false) const handleSubmit = () => { form.validateFields((errors, values) => { @@ -134,7 +222,7 @@ const AppointmentCard: React.VFC = ({ orderProductId, appo .then(() => { onRefetch && onRefetch() setIssueModalVisible(false) - message.success(appointmentMessages['*'].saveSuccessfully) + message.success(formatMessage(appointmentMessages['*'].saveSuccessfully)) }) .finally(() => setLoading(false)) }) @@ -145,11 +233,53 @@ const AppointmentCard: React.VFC = ({ orderProductId, appo cancelAppointment(canceledReason) .then(() => { onRefetch && onRefetch() + refetchOrderProduct() + refetchAppointmentPlan() setCancelModalVisible(false) }) .finally(() => setLoading(false)) } + const handleReschedule = async () => { + setLoading(true) + + try { + if (rescheduleAppointment) { + await updateAppointmentPeriod( + rescheduleAppointment.periodStartedAt, + rescheduleAppointment.periodEndedAt, + orderProduct.startedAt, + currentMemberId || '', + ) + onRefetch && onRefetch() + await refetchAppointmentPlan() + setRescheduleModalVisible(false) + handleRescheduleCancel() + await refetchOrderProduct() + setConfirm(true) + setLoading(false) + await axios.post( + `${process.env.REACT_APP_API_BASE_ROOT}/product/${orderProductId}/reschedule`, + {}, + { + headers: { authorization: `Bearer ${authToken}` }, + }, + ) + } + } catch (error) { + console.log(error) + } + } + + const handleRescheduleCancel = () => { + setRescheduleAppointment({ + rescheduleAppointment: false, + periodStartedAt: null, + periodEndedAt: null, + appointmentPlanId: '', + }) + } + if (loadingOrderProduct) { return ( @@ -199,31 +329,30 @@ const AppointmentCard: React.VFC = ({ orderProductId, appo + {isCanceled ? ( - {formatMessage(appointmentMessages.AppointmentCard.appointmentCanceledNotation, { time: moment(orderProduct.canceledAt).format('MM/DD(dd) HH:mm'), })} ) : isFinished ? ( - <> - - {formatMessage(appointmentMessages['*'].finished)} - + {formatMessage(appointmentMessages['*'].finished)} ) : ( <> - + + + )} + {/* cancel modal */} - handleCancel()} onCancel={() => setCancelModalVisible(false)} + footer={null} > {formatMessage(appointmentMessages.AppointmentCard.confirmCancelAlert)} @@ -394,7 +527,154 @@ const AppointmentCard: React.VFC = ({ orderProductId, appo
{formatMessage(appointmentMessages.AppointmentCard.confirmCancelNotation)}
{formatMessage(appointmentMessages.AppointmentCard.canceledReason)}