Skip to content

Commit

Permalink
Display a calendar component for the vendors
Browse files Browse the repository at this point in the history
  • Loading branch information
beverloo committed Apr 19, 2024
1 parent 489c712 commit eb5fa5d
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 11 deletions.
5 changes: 5 additions & 0 deletions app/api/event/schedule/getSchedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ const kVendorTeam = z.object({
})),
});

/**
* Type definition of the public schedule information for a particular vendor team.
*/
export type PublicVendorSchedule = z.infer<typeof kVendorTeam>['schedule'];

/**
* Interface definition for the information contained within a public schedule.
*/
Expand Down
77 changes: 69 additions & 8 deletions app/schedule/[event]/components/CalendarPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,22 @@

'use client';

import { useMemo } from 'react';
import { useTheme } from '@mui/material/styles';

import Box from '@mui/material/Box';
import CloseIcon from '@mui/icons-material/Close';
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import IconButton from '@mui/material/IconButton';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import useMediaQuery from '@mui/material/useMediaQuery';

import type { PublicVendorSchedule } from '@app/api/event/schedule/getSchedule';
import { Temporal } from '@lib/Temporal';
import { Calendar, type CalendarEvent } from '@beverloo/volunteer-manager-timeline';
import '@beverloo/volunteer-manager-timeline/dist/volunteer-manager-timeline.css';

/**
* Props accepted by the <CalendarPopover> component.
*/
Expand All @@ -21,6 +28,16 @@ export interface CalendarPopoverProps {
*/
open?: boolean;

/**
* The schedule that should be displayed on this calendar.
*/
schedule: PublicVendorSchedule;

/**
* Timezone in which the calendar should be displayed.
*/
timezone: string;

/**
* Title that should be shown on the popover.
*/
Expand All @@ -35,15 +52,60 @@ export interface CalendarPopoverProps {
/**
* The <CalendarPopover> component displays a popover element containing a calendar.
*/
export function CalendarPopover(props: CalendarPopoverProps) {
const { open, onClose, title } = props;
export default function CalendarPopover(props: CalendarPopoverProps) {
const { open, onClose, schedule, timezone, title } = props;

const isMobile = useMediaQuery((theme: any) => theme.breakpoints.down('md'));
const theme = useTheme();

const { events, min, max } = useMemo(() => {
const availableColours = [
'#c9974e', '#925d65', '#6a7b73', '#a87059', '#cea19c',
'#3288bd', '#154655', '#9e0142', '#66c2a5', '#e6f598'
];

const events: CalendarEvent[] = [];
let min: Temporal.Instant | undefined;
let max: Temporal.Instant | undefined;

let id = 0;
for (const vendor of schedule) {
const title = vendor.name;
const color = availableColours.shift();

for (const shift of vendor.shifts) {
const startInstant = Temporal.Instant.fromEpochSeconds(shift.start);
if (!min || min.epochSeconds > startInstant.epochSeconds)
min = startInstant;

const endInstant = Temporal.Instant.fromEpochSeconds(shift.end);
if (!max || max.epochSeconds < endInstant.epochSeconds)
max = endInstant;

events.push({
id: id++,
start: startInstant.toString(),
end: endInstant.toString(),

title,
color,
});
}
}

return {
events,
min: min!.toZonedDateTimeISO(timezone).with({ hour: 0, minute: 0, second: 0 })
.toString({ timeZoneName: 'never' }),
max: max!.toZonedDateTimeISO(timezone).with({ hour: 23, minute: 59, second: 59 })
.toString({ timeZoneName: 'never' }),
};
}, [ schedule, timezone ]);

return (
<Dialog open={!!open} onClose={onClose} fullScreen={isMobile} fullWidth maxWidth="md">
<Stack direction="row" justifyContent="space-between" alignItems="center">
<DialogTitle>
<DialogTitle sx={{ pb: 0 }}>
{title}
</DialogTitle>
<Box sx={{ pr: 2 }}>
Expand All @@ -52,10 +114,9 @@ export function CalendarPopover(props: CalendarPopoverProps) {
</IconButton>
</Box>
</Stack>
<Typography sx={{ color: 'text.disabled', px: 3, pb: 3 }}>
{ /* TODO: Show a calendar component */ }
Calendar coming soon
</Typography>
<Calendar displayTimezone={timezone} min={min} max={max} temporal={Temporal}
events={events} view={ isMobile ? 'mobile' : 'desktop' }
theme={theme.palette.mode} />
</Dialog>
);
}
12 changes: 9 additions & 3 deletions app/schedule/[event]/components/OverviewVendorCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
'use client';

import React, { useCallback, useContext, useState } from 'react';
import dynamic from 'next/dynamic';

import Card from '@mui/material/Card';
import CardActionArea from '@mui/material/CardActionArea';
Expand All @@ -12,7 +13,6 @@ import LocalHospitalIcon from '@mui/icons-material/LocalHospital';
import ReadMoreIcon from '@mui/icons-material/ReadMore';
import SecurityIcon from '@mui/icons-material/Security';

import { CalendarPopover } from './CalendarPopover';
import { ScheduleContext } from '../ScheduleContext';
import { VendorTeam } from '@lib/database/Types';
import { concatenateNames } from '../lib/concatenateNames';
Expand All @@ -33,6 +33,11 @@ const kVendorCardIcon: { [k in VendorTeam]: React.ReactNode } = {
[VendorTeam.Security]: <SecurityIcon color="primary" />,
};

/**
* Lazily import the calendar popover, as it's not something that most people require.
*/
const LazyCalendarPopover = dynamic(() => import('./CalendarPopover'), { ssr: false });

/**
* Props accepted by the <OverviewVendorCard> component.
*/
Expand Down Expand Up @@ -90,8 +95,9 @@ export function OverviewVendorCard(props: OverviewVendorCardProps) {

</CardActionArea>
</Card>
<CalendarPopover open={calendarOpen} onClose={handleCloseCalendar}
title={title} />
<LazyCalendarPopover open={calendarOpen} onClose={handleCloseCalendar}
schedule={vendor.schedule} timezone={schedule.config.timezone}
title={title} />
</>
);
}

0 comments on commit eb5fa5d

Please sign in to comment.