gear-orders/web/vite/src/OrderSets.tsx

235 lines
7 KiB
TypeScript
Raw Normal View History

import React from "react";
2026-03-07 18:38:44 +00:00
import {
Title,
Card,
Text,
Flex,
Badge,
Box,
RingProgress,
Alert,
2026-03-07 18:38:44 +00:00
} from "@mantine/core";
import { IconAlertTriangle } from "@tabler/icons-react";
import { TimeValue } from "@mantine/dates";
import { IconPencil, IconPlus, IconTrash } from "@tabler/icons-react";
import { ConfirmDialogButton } from "./ConfirmDialogButton";
import { NavigateButton } from "./NavigateButton";
import { Link, useFetcher } from "react-router";
import { useUserContext } from "./UserContext";
2026-03-07 23:54:41 +00:00
import { DonutChart } from "@mantine/charts";
const COLORS_ROTATION = [
"teal",
"pink",
"lime",
"violet",
"orange",
"blue",
"yellow",
"grape",
"green",
"red",
"cyan",
"gray",
];
export interface OrderSetProps {
orderSets: (Pick<
OrderSet,
2026-03-07 18:38:44 +00:00
| "id"
| "name"
| "scheduled"
| "time"
| "weekends"
| "weekdays"
| "orders"
| "probability"
> & {
2026-03-07 23:54:41 +00:00
orders: Pick<OrderSetOrder, "id" | "name" | "weight">;
2026-03-07 18:38:44 +00:00
punishment_pool_name: string;
})[];
username: string;
linkBack?: React.ReactNode;
}
export const OrderSets: React.FC<OrderSetProps> = ({
orderSets,
username,
linkBack,
}) => {
const { username: current_user } = useUserContext();
const fetcher = useFetcher();
const handleDelete = React.useCallback(
(id: number) => {
fetcher.submit(null, {
action: `/orders/${username}/${id}`,
method: "DELETE",
});
},
[fetcher],
);
const [isMastodonSet, setIsMastodonSet] = React.useState(true);
React.useEffect(() => {
2026-03-07 23:54:41 +00:00
if (username) {
fetch(`/api/subs/${username}`)
.then((response) => response.json())
.then((data) => {
if (!data.mastodon_server || !data.mastodon_username) {
setIsMastodonSet(false);
}
});
}
}, [username]);
2026-03-07 23:54:41 +00:00
const [portalRef, setPortalRef] = React.useState<HTMLElement | null>();
return (
<>
<Box mb="lg">
<Title order={1}>Order Sets for {username}</Title>
{linkBack ? linkBack : null}
</Box>
{orderSets.length > 0 && isMastodonSet ? null : (
<Flex justify="center">
<Alert
variant="light"
color="orange"
title="Warning"
icon={<IconAlertTriangle />}
my="md"
w="40rem"
>
<b>{username}</b> must authorize with a Mastodon account before
orders can be issued.
{username === current_user ? (
<>
<br />
<Link to="/profile/">Edit Profile</Link>
</>
) : null}
</Alert>
</Flex>
)}
2026-03-07 23:54:41 +00:00
<div
ref={(node) => {
if (portalRef == null && node != null) {
setPortalRef(node);
}
}}
>
<Flex gap="md" wrap="wrap">
{orderSets
? orderSets.map(
({
id,
name,
scheduled,
orders,
time,
weekdays,
weekends,
probability,
punishment_pool_name,
}) => (
<Card
key={id}
shadow="sm"
padding="lg"
radius="md"
withBorder
bg="gray.2"
w="400px"
mb="0"
>
<Flex direction="column" gap="md" h="100%">
<Title order={4}>{name}</Title>
{scheduled ? (
<>
<Flex gap="xs">
<Text>
<b>Scheduled:</b>
</Text>
{time.split(",").map((time) => (
<div key={time}>
<TimeValue
key={time}
value={time}
format="12h"
/>
</div>
))}
</Flex>
<Flex gap="md" align="center">
{weekdays ? (
<Badge color="blue">Weekdays</Badge>
) : null}
{weekends ? (
<Badge color="blue">Weekends</Badge>
) : null}
<RingProgress
size={30}
thickness={5}
label={
<Text size="xs" ml="lg">
{probability * 100}%
</Text>
}
sections={[
{ color: "cyan", value: probability * 100 },
]}
/>
</Flex>
</>
) : null}
{punishment_pool_name ? (
<Text flex={1}>
<b>Punishments:</b> {punishment_pool_name}
</Text>
) : null}
<Flex justify="end" align="flex-end" gap="md">
{orders.length > 0 ? (
<DonutChart
flex={1}
size={130}
thickness={30}
data={orders.map(({ name, weight }, idx) => ({
name,
value: weight,
color:
COLORS_ROTATION[idx % COLORS_ROTATION.length],
}))}
tooltipDataSource="segment"
2026-03-07 18:38:44 +00:00
/>
2026-03-07 23:54:41 +00:00
) : null}
<ConfirmDialogButton
buttonColor="red.8"
buttonText="Delete"
text={`Are you sure you want to delete ${name}?`}
onConfirm={() => handleDelete(id)}
>
<IconTrash />
</ConfirmDialogButton>
<NavigateButton to={`/orders/${username}/${id}`}>
<IconPencil style={{ marginRight: "0.5rem" }} />
Edit
</NavigateButton>
</Flex>
</Flex>
2026-03-07 23:54:41 +00:00
</Card>
),
)
: null}
</Flex>
</div>
<Box my="lg">
<NavigateButton to={`/orders/${username}/new`}>
<IconPlus style={{ marginRight: "0.5rem" }} />
New
</NavigateButton>
</Box>
</>
);
};