car-crushers-portal/app/routes/events-calendar.tsx

229 lines
6.7 KiB
TypeScript

import calendarStyles from "react-big-calendar/lib/css/react-big-calendar.css";
import calendarOverrides from "../styles/calendar.css";
import eventStyles from "../styles/events-team.css";
import { Calendar, dayjsLocalizer } from "react-big-calendar";
import dayjs from "dayjs";
import { type LinksFunction } from "@remix-run/cloudflare";
import {
Accordion,
AccordionButton,
AccordionIcon,
AccordionItem,
AccordionPanel,
Box,
Button,
Container,
Heading,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Text,
useDisclosure,
} from "@chakra-ui/react";
import { useLoaderData } from "@remix-run/react";
import { useState } from "react";
import utc from "dayjs/plugin/utc.js";
export const links: LinksFunction = () => {
return [
{ href: calendarStyles, rel: "stylesheet" },
{ href: eventStyles, rel: "stylesheet" },
{ href: calendarOverrides, rel: "stylesheet" },
];
};
export async function loader({ context }: { context: RequestContext }) {
if (!context.data.current_user)
throw new Response(null, {
status: 401,
});
if (
![1 << 3, 1 << 4, 1 << 12].find(
(p) => context.data.current_user.permissions & p,
)
)
throw new Response(null, {
status: 403,
});
const now = new Date();
const eventsData = await context.env.D1.prepare(
"SELECT answer, approved, created_by, day, details, id, month, pending, performed_at, reached_minimum_player_count, type, year FROM events WHERE month = ? AND year = ? ORDER BY day;",
)
.bind(now.getUTCMonth() + 1, now.getUTCFullYear())
.all();
if (eventsData.error)
throw new Response(null, {
status: 500,
});
const calendarData = eventsData.results.map((e) => {
return {
id: e.id,
title: (e.type as string).toUpperCase(),
allDay: true,
// A Date object will not survive being passed to the client
start: `${e.year}-${(e.month as number).toString().padStart(2, "0")}-${(e.day as number).toString().padStart(2, "0")}T00:00:00.000Z`,
end: `${e.year}-${(e.month as number).toString().padStart(2, "0")}-${(e.day as number).toString().padStart(2, "0")}T00:00:00.000Z`,
};
});
const memberData = await context.env.D1.prepare(
"SELECT id, name FROM et_members WHERE id IN (SELECT created_by FROM events WHERE month = ? AND year = ?);",
)
.bind(now.getUTCMonth() + 1, now.getUTCFullYear())
.all();
return {
calendarData,
eventList: eventsData.results,
memberData: memberData.results,
};
}
export default function () {
const data = useLoaderData<typeof loader>();
const [eventData, setEventData] = useState({} as { [k: string]: any });
const [todayFOTD, setTodayFOTD] = useState("None");
const [todayGameNight, setTodayGameNight] = useState("None");
const [todayQOTD, setTodayQOTD] = useState("None");
const [todayROTW, setTodayROTW] = useState("None");
const { isOpen, onClose, onOpen } = useDisclosure();
dayjs.extend(utc);
return (
<Container maxW="container.lg" h="600px">
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Event Info</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Heading size="md">Host</Heading>
<Text>
{
(
data.memberData.find(
(m) => m.id === eventData.created_by,
) as {
[k: string]: any;
}
)?.name
}
</Text>
<br />
<Heading size="md">Event Type</Heading>
<Text>{eventData?.type?.toUpperCase()}</Text>
<br />
<Heading size="md">Details</Heading>
<Text>{eventData?.details}</Text>
<br />
{eventData?.type === "rotw" ? (
<>
<Heading size="md">Answer</Heading>
<Text>{eventData.answer}</Text>
</>
) : null}
</ModalBody>
<ModalFooter>
<Button onClick={onClose}>Close</Button>
</ModalFooter>
</ModalContent>
</Modal>
<Calendar
endAccessor={(event) => new Date(event.end)}
events={data.calendarData}
localizer={dayjsLocalizer(dayjs)}
onSelectEvent={(e) => {
setEventData(
data.eventList.find((ev) => ev.id === e.id) as { [k: string]: any },
);
onOpen();
}}
onSelectSlot={(s) => {
const day = s.start.getUTCDate();
const month = s.start.getUTCMonth() + 1;
for (const [type, setter] of Object.entries({
fotd: setTodayFOTD,
gamenight: setTodayGameNight,
qotd: setTodayQOTD,
rotw: setTodayROTW,
})) {
const event = data.eventList.find(
(ev) => ev.type === type && ev.day === day && ev.month === month,
);
if (!event) continue;
setter(
event.type === "rotw"
? `${event.details}\n\nAnswer: ${event.answer}`
: (event.details as string),
);
}
}}
popup
startAccessor={(event) => new Date(event.start)}
style={{ height: 500 }}
toolbar={false}
views={["month"]}
/>
<Accordion id="events-accordion" mt="16px">
<AccordionItem>
<h2>
<AccordionButton>
<Box as="span" flex="1" textAlign="left">
Fact of the Day
</Box>
<AccordionIcon />
</AccordionButton>
</h2>
<AccordionPanel pb={4}>
{todayFOTD}
<br />
</AccordionPanel>
</AccordionItem>
<AccordionItem>
<h2>
<AccordionButton>
<Box as="span" flex="1" textAlign="left">
Gamenight
</Box>
<AccordionIcon />
</AccordionButton>
</h2>
<AccordionPanel pb={4}>{todayGameNight}</AccordionPanel>
</AccordionItem>
<AccordionItem>
<h2>
<AccordionButton>
<Box as="span" flex="1" textAlign="left">
Riddle of the Week
</Box>
</AccordionButton>
</h2>
<AccordionPanel pb={4}>{todayROTW}</AccordionPanel>
</AccordionItem>
<AccordionItem>
<h2>
<AccordionButton>
<Box as="span" flex="1" textAlign="left">
Question of the Day
</Box>
</AccordionButton>
</h2>
<AccordionPanel pb={4}>{todayQOTD}</AccordionPanel>
</AccordionItem>
</Accordion>
</Container>
);
}