Skip to content
Permalink
84e7df6c30
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
303 lines (271 sloc) 7.83 KB
import {
Alert,
AlertIcon,
Box,
Button,
Heading,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
StackDivider,
Table,
TableCaption,
TableContainer,
Tbody,
Td,
Th,
Thead,
Tr,
useDisclosure,
useToast,
VStack,
} from "@chakra-ui/react";
import { useLoaderData } from "@remix-run/react";
import { useEffect, useState } from "react";
export async function loader({ context }: { context: RequestContext }) {
const now = new Date();
let month = now.getUTCMonth();
let year = now.getUTCFullYear();
if (month === 0) {
month = 12;
year--;
}
const data = await context.env.D1.prepare(
"SELECT day, details, id FROM events WHERE approved = 1 AND month = ? AND year = ? AND (performed_at IS NULL OR (reached_minimum_player_count = 0 AND type = 'gamenight')) ORDER BY day;",
)
.bind(month, year)
.all();
return {
events: data.results as Record<string, string | number>[],
past_cutoff: now.getUTCDate() > 7,
};
}
export default function () {
const { events, past_cutoff } = useLoaderData<typeof loader>();
const { isOpen, onClose, onOpen } = useDisclosure();
const [eventData, setEventData] = useState({} as { [k: string]: any });
const [isBrowserSupported, setIsBrowserSupported] = useState(true);
const toast = useToast();
useEffect(() => {
if (typeof structuredClone === "undefined") setIsBrowserSupported(false);
}, []);
async function displayErrorToast(response: Response, title: string) {
let msg = "Unknown error";
try {
msg = ((await response.json()) as { error: string }).error;
} catch {}
toast({
description: msg,
status: "error",
title,
});
}
function getStatus(event: { [k: string]: string | number }) {
if (!event.performed_at) return "Approved";
if (event.type === "rotw" && event.answered_at) return "Solved";
if (event.type === "gamenight" && event.areached_minimum_player_count)
return "Certified";
return "Completed";
}
async function certify() {
const response = await fetch(
`/api/events-team/events/${eventData.id}/certify`,
{
body: "{}",
headers: {
"content-type": "application/json",
},
method: "POST",
},
);
if (!response.ok) {
await displayErrorToast(response, "Failed to certify game night");
return;
}
toast({
status: "success",
title: "Game night certified",
});
const newData = structuredClone(eventData);
newData.reached_minimum_player_count = 1;
setEventData(newData);
}
async function completed() {
const response = await fetch(
`/api/events-team/events/${eventData.id}/complete`,
{
body: "{}",
headers: {
"content-type": "application/json",
},
method: "POST",
},
);
if (!response.ok) {
await displayErrorToast(response, "Failed to mark as completed");
return;
}
toast({
status: "success",
title: "Event marked as complete",
});
const newData = structuredClone(eventData);
newData.performed_at = Date.now();
setEventData(newData);
}
async function forgotten() {
const response = await fetch(
`/api/events-team/events/${eventData.id}/forgotten`,
{
body: "{}",
headers: {
"content-type": "application/json",
},
method: "POST",
},
);
if (!response.ok) {
await displayErrorToast(response, "Failed to mark as forgotten");
return;
}
toast({
title: "Event marked as forgotten",
status: "success",
});
const newData = structuredClone(eventData);
newData.performed_at = 0;
setEventData(newData);
}
async function solve() {
const response = await fetch(
`/api/events-team/events/${eventData.id}/solve`,
{
body: "{}",
headers: {
"content-type": "application/json",
},
method: "POST",
},
);
if (!response.ok) {
await displayErrorToast(response, "Failed to mark as solved");
return;
}
toast({
status: "success",
title: "Riddle marked as solved",
});
const newData = structuredClone(eventData);
newData.answered_at = Date.now();
setEventData(newData);
}
return (
<>
<Alert display={past_cutoff ? undefined : "none"} status="warning">
<AlertIcon />
The cutoff period for retroactively actioning events has passed.
</Alert>
<Alert display={isBrowserSupported ? "none" : undefined} status="error">
<AlertIcon />
This browser is unsupported. Please upgrade to a browser not several
years out of date.
</Alert>
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Action Menu</ModalHeader>
<ModalCloseButton />
<ModalBody>
<VStack divider={<StackDivider />}>
<Box gap="8px">
<Heading size="xs">Completion</Heading>
<Button
disabled={typeof eventData.completed_at === "number"}
onClick={async () => await completed()}
>
Mark as Complete
</Button>
<Button
disabled={typeof eventData.completed_at === "number"}
onClick={async () => await forgotten()}
>
Mark as Forgotten
</Button>
</Box>
{eventData.type === "rotw" ? (
<Box gap="8px">
<Heading size="xs">Solved Status</Heading>
<Button
disabled={Boolean(eventData.answered_at)}
onClick={async () => await solve()}
>
{eventData.answered_at ? "Solved" : "Mark as Solved"}
</Button>
</Box>
) : null}
{eventData.type === "gamenight" ? (
<Box gap="8px">
<Heading size="xs">Certified Status</Heading>
<Button
disabled={Boolean(eventData.reached_minimum_player_count)}
onClick={async () => await certify()}
>
{eventData.reached_minimum_player_count
? "Certified"
: "Certify"}
</Button>
</Box>
) : null}
</VStack>
</ModalBody>
<ModalFooter>
<Button onClick={onClose}>Close</Button>
</ModalFooter>
</ModalContent>
</Modal>
<TableContainer>
<Table variant="simple">
<TableCaption>
Events that are not denied or left pending which need to be actioned
</TableCaption>
<Thead>
<Tr>
<Th>Day</Th>
<Th>Details</Th>
<Th>Current Status</Th>
<Th>Action</Th>
</Tr>
</Thead>
<Tbody>
{events.map((event) => (
<Tr>
<Td>{event.day}</Td>
<Td>
{(event.details as string).length > 100
? `${(event.details as string).substring(0, 97)}...`
: event.details}
</Td>
<Td>{getStatus(event)}</Td>
<Td>
<Button
disabled={past_cutoff}
onClick={() => {
setEventData(event);
onOpen();
}}
>
Action Menu
</Button>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</>
);
}