Orders Pools - Improve UI

This commit is contained in:
Johnny Gear 2026-03-07 17:54:41 -06:00
parent 257461307e
commit b194b19a15
5 changed files with 153 additions and 92 deletions

View file

@ -140,6 +140,8 @@ def my_order_sets():
'punishment_pool_name': op.punishment_pool.name if op.punishment_pool is not None else None, 'punishment_pool_name': op.punishment_pool.name if op.punishment_pool is not None else None,
'orders': [{ 'orders': [{
'id': order.id, 'id': order.id,
'name': order.name,
'weight': order.weight
} for order in op.orders] } for order in op.orders]
} }
for op for op
@ -162,6 +164,8 @@ def sub_order_sets(username, sub):
'punishment_pool_name': op.punishment_pool.name if op.punishment_pool is not None else None, 'punishment_pool_name': op.punishment_pool.name if op.punishment_pool is not None else None,
'orders': [{ 'orders': [{
'id': order.id, 'id': order.id,
'name': order.name,
'weight': order.weight
} for order in op.orders] } for order in op.orders]
} }
for op for op

View file

@ -14,6 +14,7 @@
"vite": "^5.0.11" "vite": "^5.0.11"
}, },
"dependencies": { "dependencies": {
"@mantine/charts": "^8.3.13",
"@mantine/core": "^8.3.12", "@mantine/core": "^8.3.12",
"@mantine/dates": "^8.3.13", "@mantine/dates": "^8.3.13",
"@mantine/form": "^8.3.13", "@mantine/form": "^8.3.13",
@ -23,6 +24,7 @@
"react": "^19.2.3", "react": "^19.2.3",
"react-dom": "^19.2.3", "react-dom": "^19.2.3",
"react-router": "^7.12.0", "react-router": "^7.12.0",
"recharts": "^3.8.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
} }

View file

@ -113,11 +113,12 @@ export const OrderSet: React.FC = () => {
} }
: { : {
scheduled: false, scheduled: false,
time: [""],
orders: [], orders: [],
}, },
validate: { validate: {
name: (value: string) => name: (value: string) =>
value.length < 1 ? "Please enter a name" : null, !value || value.length < 1 ? "Please enter a name" : null,
time: (value: string, values: FormOrderSet) => time: (value: string, values: FormOrderSet) =>
values.scheduled && (!value || value.length < 1 || !value[0]) values.scheduled && (!value || value.length < 1 || !value[0])
? "Please set a time" ? "Please set a time"
@ -281,7 +282,7 @@ export const OrderSet: React.FC = () => {
label="Time" label="Time"
error={form.getInputProps("time").error} error={form.getInputProps("time").error}
> >
{(form.getValues().time as string[]).map((time, idx) => ( {((form.getValues().time ?? []) as string[]).map((time, idx) => (
<Flex key={idx} gap="xs"> <Flex key={idx} gap="xs">
<TimeInput <TimeInput
key={time} key={time}

View file

@ -17,6 +17,22 @@ import { ConfirmDialogButton } from "./ConfirmDialogButton";
import { NavigateButton } from "./NavigateButton"; import { NavigateButton } from "./NavigateButton";
import { Link, useFetcher } from "react-router"; import { Link, useFetcher } from "react-router";
import { useUserContext } from "./UserContext"; import { useUserContext } from "./UserContext";
import { DonutChart } from "@mantine/charts";
const COLORS_ROTATION = [
"teal",
"pink",
"lime",
"violet",
"orange",
"blue",
"yellow",
"grape",
"green",
"red",
"cyan",
"gray",
];
export interface OrderSetProps { export interface OrderSetProps {
orderSets: (Pick< orderSets: (Pick<
@ -30,7 +46,7 @@ export interface OrderSetProps {
| "orders" | "orders"
| "probability" | "probability"
> & { > & {
orders: Pick<OrderSetOrder, "id" | "name">; orders: Pick<OrderSetOrder, "id" | "name" | "weight">;
punishment_pool_name: string; punishment_pool_name: string;
})[]; })[];
username: string; username: string;
@ -56,6 +72,7 @@ export const OrderSets: React.FC<OrderSetProps> = ({
const [isMastodonSet, setIsMastodonSet] = React.useState(true); const [isMastodonSet, setIsMastodonSet] = React.useState(true);
React.useEffect(() => { React.useEffect(() => {
if (username) {
fetch(`/api/subs/${username}`) fetch(`/api/subs/${username}`)
.then((response) => response.json()) .then((response) => response.json())
.then((data) => { .then((data) => {
@ -63,8 +80,11 @@ export const OrderSets: React.FC<OrderSetProps> = ({
setIsMastodonSet(false); setIsMastodonSet(false);
} }
}); });
}
}, [username]); }, [username]);
const [portalRef, setPortalRef] = React.useState<HTMLElement | null>();
return ( return (
<> <>
<Box mb="lg"> <Box mb="lg">
@ -92,6 +112,13 @@ export const OrderSets: React.FC<OrderSetProps> = ({
</Alert> </Alert>
</Flex> </Flex>
)} )}
<div
ref={(node) => {
if (portalRef == null && node != null) {
setPortalRef(node);
}
}}
>
<Flex gap="md" wrap="wrap"> <Flex gap="md" wrap="wrap">
{orderSets {orderSets
? orderSets.map( ? orderSets.map(
@ -114,16 +141,23 @@ export const OrderSets: React.FC<OrderSetProps> = ({
withBorder withBorder
bg="gray.2" bg="gray.2"
w="400px" w="400px"
mb="0"
> >
<Flex direction="column" gap="md" h="100%"> <Flex direction="column" gap="md" h="100%">
<Title order={4}>{name}</Title> <Title order={4}>{name}</Title>
{scheduled ? ( {scheduled ? (
<> <>
<Flex gap="xs"> <Flex gap="xs">
<Text>Scheduled:</Text> <Text>
<b>Scheduled:</b>
</Text>
{time.split(",").map((time) => ( {time.split(",").map((time) => (
<div key={time}> <div key={time}>
<TimeValue key={time} value={time} format="12h" /> <TimeValue
key={time}
value={time}
format="12h"
/>
</div> </div>
))} ))}
</Flex> </Flex>
@ -135,8 +169,13 @@ export const OrderSets: React.FC<OrderSetProps> = ({
<Badge color="blue">Weekends</Badge> <Badge color="blue">Weekends</Badge>
) : null} ) : null}
<RingProgress <RingProgress
size={80} size={30}
label={<Text size="xs">{probability * 100}%</Text>} thickness={5}
label={
<Text size="xs" ml="lg">
{probability * 100}%
</Text>
}
sections={[ sections={[
{ color: "cyan", value: probability * 100 }, { color: "cyan", value: probability * 100 },
]} ]}
@ -144,14 +183,26 @@ export const OrderSets: React.FC<OrderSetProps> = ({
</Flex> </Flex>
</> </>
) : null} ) : null}
<Text mb="md" flex={1}> {punishment_pool_name ? (
Orders: {orders.length} <Text flex={1}>
<br /> <b>Punishments:</b> {punishment_pool_name}
{punishment_pool_name
? `Punishments: ${punishment_pool_name}`
: null}
</Text> </Text>
<Flex gap="md" justify="end"> ) : 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"
/>
) : null}
<ConfirmDialogButton <ConfirmDialogButton
buttonColor="red.8" buttonColor="red.8"
buttonText="Delete" buttonText="Delete"
@ -171,11 +222,13 @@ export const OrderSets: React.FC<OrderSetProps> = ({
) )
: null} : null}
</Flex> </Flex>
</div>
<Box my="lg">
<NavigateButton to={`/orders/${username}/new`}> <NavigateButton to={`/orders/${username}/new`}>
<IconPlus style={{ marginRight: "0.5rem" }} /> <IconPlus style={{ marginRight: "0.5rem" }} />
New New
</NavigateButton> </NavigateButton>
<Box mb="lg"></Box> </Box>
</> </>
); );
}; };

View file

@ -14,6 +14,7 @@ import { Notifications } from "@mantine/notifications";
import "@mantine/core/styles.css"; import "@mantine/core/styles.css";
import "@mantine/dates/styles.css"; import "@mantine/dates/styles.css";
import "@mantine/notifications/styles.css"; import "@mantine/notifications/styles.css";
import "@mantine/charts/styles.css";
import { Dashboard, subsListLoader } from "./Dashboard"; import { Dashboard, subsListLoader } from "./Dashboard";
import { SubOrderSets, subOrderSetsLoader } from "./SubOrderSets"; import { SubOrderSets, subOrderSetsLoader } from "./SubOrderSets";