Skip to content

Commit

Permalink
Merge branch 'release'
Browse files Browse the repository at this point in the history
  • Loading branch information
LoisChen68 committed Oct 5, 2023
2 parents fff557e + 0ed0063 commit 59e4f2d
Show file tree
Hide file tree
Showing 14 changed files with 1,525 additions and 376 deletions.
257 changes: 115 additions & 142 deletions src/components/appointment/AppointmentCard.tsx

Large diffs are not rendered by default.

37 changes: 21 additions & 16 deletions src/components/appointment/AppointmentCollectionTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ const StyledTimeStandardBlock = styled.div`
`

const AppointmentCollectionTabs: React.VFC<{
creatorId: string
appointmentPlans: (AppointmentPlan & { periods: AppointmentPeriod[] })[]
}> = ({ appointmentPlans }) => {
}> = ({ creatorId, appointmentPlans }) => {
const { formatMessage } = useIntl()
const [selectedAppointmentPlanId, setSelectedAppointmentPlanId] = useState<string | null>(appointmentPlans[0].id)
const { search } = useLocation()
Expand Down Expand Up @@ -145,6 +146,7 @@ const AppointmentCollectionTabs: React.VFC<{
</div>

<AppointmentPlanCollection
creatorId={creatorId}
appointmentPlans={appointmentPlans
.filter(v =>
appointmentPlanId ? v.id === appointmentPlanId || v.isPrivate === false : v.isPrivate === false,
Expand All @@ -158,8 +160,9 @@ const AppointmentCollectionTabs: React.VFC<{
}

export const AppointmentPlanCollection: React.FC<{
creatorId: string
appointmentPlans: (AppointmentPlan & { periods: AppointmentPeriod[] })[]
}> = ({ appointmentPlans }) => {
}> = ({ creatorId, appointmentPlans }) => {
const { formatMessage } = useIntl()
const { id: appId } = useApp()
const { isAuthenticated } = useAuth()
Expand All @@ -168,14 +171,6 @@ export const AppointmentPlanCollection: React.FC<{

const [selectedPeriod, setSelectedPeriod] = useState<AppointmentPeriod | null>(null)

const diffPlanBookedTimes = [
...appointmentPlans.map(appointmentPlan =>
appointmentPlan.periods
.filter(period => Boolean(period.booked >= appointmentPlan.capacity && appointmentPlan.capacity !== -1))
.map(v => moment(v.startedAt).format('YYYY-MM-DD HH:mm')),
),
].flat(1)

const { resourceCollection } = useResourceCollection(
appId ? appointmentPlans.map(appointmentPlan => `${appId}:appointment_plan:${appointmentPlan.id}`) : [],
true,
Expand Down Expand Up @@ -206,9 +201,15 @@ export const AppointmentPlanCollection: React.FC<{
startedAt={selectedPeriod?.startedAt}
renderTrigger={({ setVisible }) => (
<AppointmentPeriodCollection
creatorId={creatorId}
appointmentPlan={{
id: appointmentPlan.id,
defaultMeetGateway: appointmentPlan.defaultMeetGateway,
reservationType: appointmentPlan.rescheduleType,
reservationAmount: appointmentPlan.rescheduleAmount,
capacity: appointmentPlan.capacity,
}}
appointmentPeriods={appointmentPlan.periods}
reservationAmount={appointmentPlan.reservationAmount}
reservationType={appointmentPlan.reservationType}
onClick={period => {
if (!isAuthenticated) {
setAuthModalVisible?.(true)
Expand All @@ -230,7 +231,6 @@ export const AppointmentPlanCollection: React.FC<{
resource && tracking.click(resource, { position: idx + 1 })
}
}}
diffPlanBookedTimes={diffPlanBookedTimes}
/>
)}
/>
Expand All @@ -239,9 +239,15 @@ export const AppointmentPlanCollection: React.FC<{
defaultProductId={`AppointmentPlan_${appointmentPlan.id}`}
renderTrigger={({ onOpen }) => (
<AppointmentPeriodCollection
creatorId={creatorId}
appointmentPlan={{
id: appointmentPlan.id,
defaultMeetGateway: appointmentPlan.defaultMeetGateway,
reservationType: appointmentPlan.rescheduleType,
reservationAmount: appointmentPlan.rescheduleAmount,
capacity: appointmentPlan.capacity,
}}
appointmentPeriods={appointmentPlan.periods}
reservationAmount={appointmentPlan.reservationAmount}
reservationType={appointmentPlan.reservationType}
onClick={period => {
if (!isAuthenticated) {
setAuthModalVisible?.(true)
Expand All @@ -263,7 +269,6 @@ export const AppointmentPlanCollection: React.FC<{
resource && tracking.click(resource, { position: idx + 1 })
}
}}
diffPlanBookedTimes={diffPlanBookedTimes}
/>
)}
startedAt={selectedPeriod?.startedAt}
Expand Down
146 changes: 122 additions & 24 deletions src/components/appointment/AppointmentItem.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
import { Spinner } from '@chakra-ui/react'
import { uniq } from 'ramda'
import React from 'react'
import { useIntl } from 'react-intl'
import styled, { css } from 'styled-components'
import { productMessages } from '../../helpers/translation'
import { useMeetByAppointmentPlanIdAndPeriod } from '../../hooks/appointment'
import { useOverlapMeets } from '../../hooks/meet'
import appointmentMessages from './translation'

const StyledItemWrapper = styled.div<{ variant?: 'default' | 'excluded' | 'disabled' }>`
const StyledItemWrapper = styled.div<{
variant?: 'bookable' | 'closed' | 'booked' | 'meetingFull'
}>`
position: relative;
margin-bottom: 0.5rem;
margin-right: 0.5rem;
padding: 0.75rem;
width: 6rem;
overflow: hidden;
border: solid 1px ${props => (props.variant === 'disabled' ? 'var(--gray-light)' : 'var(--gray-dark)')};
color: ${props => (props.variant === 'disabled' ? 'var(--gray-dark)' : 'var(--gray-darker)')};
border: solid 1px
${props =>
props.variant === 'booked' || props.variant === 'meetingFull' ? 'var(--gray-light)' : 'var(--gray-dark)'};
color: ${props =>
props.variant === 'booked' || props.variant === 'meetingFull' ? 'var(--gray-dark)' : 'var(--gray-darker)'};
border-radius: 4px;
cursor: ${props => (props.variant !== 'default' ? 'not-allowed' : 'pointer')};
cursor: ${props => (props.variant !== 'bookable' ? 'not-allowed' : 'pointer')};
${props =>
props.variant === 'excluded'
props.variant === 'closed'
? css`
::before {
display: block;
Expand Down Expand Up @@ -46,28 +55,117 @@ const StyledItemMeta = styled.div`
`

const AppointmentItem: React.VFC<{
id: string
startedAt: Date
creatorId: string
appointmentPlan: {
id: string
capacity: number
defaultMeetGateway: string
}
period: {
startedAt: Date
endedAt: Date
}
services: { id: string; gateway: string }[]
loadingServices?: boolean
isPeriodExcluded?: boolean
isEnrolled?: boolean
isExcluded?: boolean
overLapPeriods?: string[]
onClick: () => void
}> = ({ startedAt, isEnrolled, isExcluded, onClick }) => {
onOverlapPeriodsChange?: (overLapPeriods: string[]) => void
}> = ({
creatorId,
appointmentPlan,
period,
services,
loadingServices,
isPeriodExcluded,
isEnrolled,
overLapPeriods,
onClick,
onOverlapPeriodsChange,
}) => {
const { formatMessage } = useIntl()

return (
<StyledItemWrapper variant={isEnrolled ? 'disabled' : isExcluded ? 'excluded' : 'default'} onClick={onClick}>
<StyledItemTitle>
{startedAt.getHours().toString().padStart(2, '0')}:{startedAt.getMinutes().toString().padStart(2, '0')}
</StyledItemTitle>
<StyledItemMeta>
{isEnrolled
? formatMessage(productMessages.appointment.status.booked)
: isExcluded
? formatMessage(productMessages.appointment.status.closed)
: formatMessage(productMessages.appointment.status.bookable)}
</StyledItemMeta>
</StyledItemWrapper>
const { loading: loadingMeetMembers, meet } = useMeetByAppointmentPlanIdAndPeriod(
appointmentPlan.id,
period.startedAt,
period.endedAt,
)
const { loading: loadingOverlapMeet, overlapMeets } = useOverlapMeets(period.startedAt, period.endedAt)
const zoomServices = services.filter(service => service.gateway === 'zoom').map(service => service.id)
const overlapCreatorMeets = overlapMeets
.filter(overlapMeet => overlapMeet.hostMemberId === creatorId)
.filter(overlapCreatorMeet => overlapCreatorMeet.target !== appointmentPlan.id)
const currentUseServices = uniq(overlapMeets.map(overlapMeet => overlapMeet.serviceId))

let variant: 'bookable' | 'closed' | 'booked' | 'meetingFull' | 'overlap' | undefined

if (overlapCreatorMeets.length >= 1)
overLapPeriods &&
!overLapPeriods.some(overLapPeriod => overLapPeriod === appointmentPlan.id) &&
onOverlapPeriodsChange?.([...overLapPeriods, appointmentPlan.id])

if (isPeriodExcluded) {
variant = 'closed'
} else if (isEnrolled) {
variant = 'booked'
} else if (overlapCreatorMeets.length >= 1) {
variant = 'overlap'
} else {
if (appointmentPlan.defaultMeetGateway === 'zoom') {
if (
zoomServices.length >= 1 &&
zoomServices.filter(zoomService => !currentUseServices.includes(zoomService)).length >= 1
) {
if (appointmentPlan.capacity === -1) {
variant = 'bookable'
} else {
if (meet) {
meet.meetMembers.length >= appointmentPlan.capacity ? (variant = 'meetingFull') : (variant = 'bookable')
} else {
variant = 'bookable'
}
}
} else {
variant = 'meetingFull'
}
} else {
if (appointmentPlan.capacity === -1) {
variant = 'bookable'
} else {
if (meet) {
meet.meetMembers.length >= appointmentPlan.capacity ? (variant = 'meetingFull') : (variant = 'bookable')
} else {
variant = 'bookable'
}
}
}
}

if (variant === 'overlap') {
return null
} else {
return (
<StyledItemWrapper variant={variant} onClick={variant === 'bookable' ? onClick : undefined}>
<StyledItemTitle>
{period.startedAt.getHours().toString().padStart(2, '0')}:
{period.startedAt.getMinutes().toString().padStart(2, '0')}
</StyledItemTitle>
<StyledItemMeta>
{loadingMeetMembers || loadingOverlapMeet || loadingServices ? (
<Spinner />
) : variant === 'booked' ? (
formatMessage(appointmentMessages.AppointmentItem.booked)
) : variant === 'meetingFull' ? (
formatMessage(appointmentMessages.AppointmentItem.meetingIsFull)
) : variant === 'bookable' ? (
formatMessage(appointmentMessages.AppointmentItem.bookable)
) : (
formatMessage(appointmentMessages.AppointmentItem.closed)
)}
</StyledItemMeta>
</StyledItemWrapper>
)
}
}

export default AppointmentItem
Loading

0 comments on commit 59e4f2d

Please sign in to comment.