241 lines
5.7 KiB
TypeScript
241 lines
5.7 KiB
TypeScript
import {
|
|
Button,
|
|
Container,
|
|
Heading,
|
|
Link,
|
|
Modal,
|
|
ModalBody,
|
|
ModalCloseButton,
|
|
ModalContent,
|
|
ModalFooter,
|
|
ModalHeader,
|
|
ModalOverlay,
|
|
Table,
|
|
TableContainer,
|
|
Tbody,
|
|
Td,
|
|
Text,
|
|
Textarea,
|
|
Th,
|
|
Thead,
|
|
Tr,
|
|
useDisclosure,
|
|
useToast,
|
|
} from "@chakra-ui/react";
|
|
import { LoaderFunctionArgs } from "@remix-run/cloudflare";
|
|
import { useLoaderData } from "@remix-run/react";
|
|
import { useState } from "react";
|
|
|
|
export async function loader({
|
|
context,
|
|
params,
|
|
}: {
|
|
context: RequestContext;
|
|
params: LoaderFunctionArgs & { uid: string };
|
|
}) {
|
|
const { current_user: user } = context.data;
|
|
|
|
if (!user)
|
|
throw new Response(null, {
|
|
status: 401,
|
|
});
|
|
|
|
if (![1 << 3, 1 << 4, 1 << 12].find((p) => user.permissions & p))
|
|
throw new Response(null, {
|
|
status: 403,
|
|
});
|
|
|
|
const strikeData = await context.env.D1.prepare(
|
|
"SELECT * FROM et_strikes WHERE user = ?;",
|
|
)
|
|
.bind(params.uid)
|
|
.all();
|
|
|
|
return {
|
|
can_manage: Boolean([1 << 4, 1 << 12].find((p) => user.permissions & p)),
|
|
strikes: strikeData.results,
|
|
user: params.uid,
|
|
};
|
|
}
|
|
|
|
export default function () {
|
|
const { can_manage, strikes, user } = useLoaderData<typeof loader>();
|
|
const [strikeData, setStrikeData] = useState(strikes);
|
|
const toast = useToast();
|
|
const [rmStrikeId, setRmStrikeId] = useState("");
|
|
const [strikeReason, setStrikeReason] = useState("");
|
|
|
|
async function removeStrike(id: string) {
|
|
const removeResp = await fetch(`/api/events-team/strikes/${id}`, {
|
|
method: "DELETE",
|
|
});
|
|
|
|
if (!removeResp.ok) {
|
|
let msg = "Unknown error";
|
|
|
|
try {
|
|
msg = ((await removeResp.json()) as { error: string }).error;
|
|
} catch {}
|
|
|
|
toast({
|
|
description: msg,
|
|
status: "error",
|
|
title: "Failed to remove strike",
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
toast({
|
|
description: `Strike ${id} was removed`,
|
|
status: "success",
|
|
title: "Strike Removed",
|
|
});
|
|
|
|
setStrikeData(strikeData.filter((strike) => strike.id !== id));
|
|
closeRmStrike();
|
|
}
|
|
|
|
async function addStrike() {
|
|
const addStrikeResp = await fetch("/api/events-team/strikes/new", {
|
|
body: JSON.stringify({
|
|
reason: strikeReason,
|
|
user,
|
|
}),
|
|
headers: {
|
|
"content-type": "application/json",
|
|
},
|
|
method: "POST",
|
|
});
|
|
|
|
if (!addStrikeResp.ok) {
|
|
let msg = "Unknown error";
|
|
|
|
try {
|
|
msg = ((await addStrikeResp.json()) as { error: string }).error;
|
|
} catch {}
|
|
|
|
toast({
|
|
description: msg,
|
|
status: "error",
|
|
title: "Failed to add strike",
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
toast({
|
|
description: "Strike added",
|
|
status: "success",
|
|
title: "Success",
|
|
});
|
|
|
|
const newStrikeData = strikeData;
|
|
|
|
newStrikeData.push(await addStrikeResp.json());
|
|
setStrikeData(newStrikeData);
|
|
closeAddStrike();
|
|
}
|
|
|
|
const {
|
|
isOpen: rmStrikeOpen,
|
|
onClose: closeRmStrike,
|
|
onOpen: openRmStrike,
|
|
} = useDisclosure();
|
|
const {
|
|
isOpen: addStrikeOpen,
|
|
onClose: closeAddStrike,
|
|
onOpen: openAddStrike,
|
|
} = useDisclosure();
|
|
|
|
return (
|
|
<Container maxW="container.lg">
|
|
<Modal isOpen={rmStrikeOpen} onClose={closeRmStrike}>
|
|
<ModalOverlay />
|
|
<ModalContent>
|
|
<ModalHeader>Remove Strike</ModalHeader>
|
|
<ModalCloseButton />
|
|
<ModalBody>
|
|
<Text>Are you sure you want to remove this strike?</Text>
|
|
</ModalBody>
|
|
<ModalFooter>
|
|
<Button mr="8px">No</Button>
|
|
<Button
|
|
colorScheme="red"
|
|
onClick={async () => await removeStrike(rmStrikeId)}
|
|
>
|
|
Yes
|
|
</Button>
|
|
</ModalFooter>
|
|
</ModalContent>
|
|
</Modal>
|
|
<Modal isOpen={addStrikeOpen} onClose={closeAddStrike}>
|
|
<ModalOverlay />
|
|
<ModalContent>
|
|
<ModalHeader>Add Strike</ModalHeader>
|
|
<ModalCloseButton />
|
|
<ModalBody>
|
|
<Heading mb="8px" size="xs">
|
|
Reason
|
|
</Heading>
|
|
<Textarea
|
|
onChange={(e) => setStrikeReason(e.target.value)}
|
|
placeholder="Strike reason"
|
|
value={strikeReason}
|
|
/>
|
|
</ModalBody>
|
|
<ModalFooter>
|
|
<Button
|
|
mr="8px"
|
|
onClick={() => {
|
|
closeAddStrike();
|
|
setStrikeReason("");
|
|
}}
|
|
>
|
|
Cancel
|
|
</Button>
|
|
<Button colorScheme="red" onClick={async () => await addStrike()}>
|
|
Add Strike
|
|
</Button>
|
|
</ModalFooter>
|
|
</ModalContent>
|
|
</Modal>
|
|
<Heading my="16px">Strikes</Heading>
|
|
<TableContainer>
|
|
<Table variant="simple">
|
|
<Thead>
|
|
<Th>Time Added</Th>
|
|
<Th>Added By</Th>
|
|
<Th>Reason</Th>
|
|
<Th>Remove</Th>
|
|
</Thead>
|
|
<Tbody>
|
|
{strikeData.map((strike: { [k: string]: any }) => (
|
|
<Tr>
|
|
<Td>{new Date(strike.created_at).toUTCString()}</Td>
|
|
<Td>{strike.created_by}</Td>
|
|
<Td>{strike.reason}</Td>
|
|
<Td>
|
|
{can_manage ? (
|
|
<Link
|
|
onClick={() => {
|
|
setRmStrikeId(strike.id);
|
|
openRmStrike();
|
|
}}
|
|
>
|
|
Remove
|
|
</Link>
|
|
) : null}
|
|
</Td>
|
|
</Tr>
|
|
))}
|
|
</Tbody>
|
|
</Table>
|
|
</TableContainer>
|
|
<Link color="#646cff" onClick={openAddStrike} py="16px">
|
|
Add Strike
|
|
</Link>
|
|
</Container>
|
|
);
|
|
}
|