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="xs">Host</Heading> <Text> { ( data.memberData.find( (m) => m.id === eventData.created_by, ) as { [k: string]: any; } )?.name } </Text> <br /> <Heading size="sm">Event Type</Heading> <Text>{eventData?.type?.toUpperCase()}</Text> <br /> <Heading size="sm">Details</Heading> <Text>{eventData?.details}</Text> <br /> {eventData?.type === "rotw" ? ( <> <Heading size="sm">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> ); }