UI improvements

This commit is contained in:
Johnny Gear 2026-01-29 16:23:20 -06:00
parent 058891dc36
commit 14009ad6d3
5 changed files with 152 additions and 114 deletions

View file

@ -90,7 +90,7 @@ def sub_order_set(username, set_id):
update_add_ons(order_to_update, updated_order['add_ons']) update_add_ons(order_to_update, updated_order['add_ons'])
else: else:
Order.create( order_to_update = Order.create(
pool=op, pool=op,
name=updated_order['name'], name=updated_order['name'],
weight=updated_order['weight'], weight=updated_order['weight'],

View file

@ -16,6 +16,7 @@
"dependencies": { "dependencies": {
"@mantine/core": "^8.3.12", "@mantine/core": "^8.3.12",
"@mantine/form": "^8.3.13", "@mantine/form": "^8.3.13",
"@mantine/notifications": "^8.3.13",
"@tabler/icons-react": "^3.36.1", "@tabler/icons-react": "^3.36.1",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"react": "^19.2.3", "react": "^19.2.3",

View file

@ -5,14 +5,23 @@ import {
Slider, Slider,
TextInput, TextInput,
Button, Button,
Divider,
Flex, Flex,
ActionIcon, ActionIcon,
Title,
Box,
Affix,
} from "@mantine/core"; } from "@mantine/core";
import { notifications } from "@mantine/notifications";
import { randomId } from "@mantine/hooks"; import { randomId } from "@mantine/hooks";
import { useForm } from "@mantine/form"; import { useForm } from "@mantine/form";
import React from "react"; import React from "react";
import { Params, useLoaderData, useParams, useNavigate } from "react-router"; import {
Params,
useLoaderData,
useParams,
useNavigate,
Link,
} from "react-router";
import { IconMinus, IconPlus } from "@tabler/icons-react"; import { IconMinus, IconPlus } from "@tabler/icons-react";
export const subOrderSetLoader = async ({ export const subOrderSetLoader = async ({
@ -21,7 +30,7 @@ export const subOrderSetLoader = async ({
params: Params<string>; params: Params<string>;
}) => }) =>
fetch(`/api/subs/${username}/sets/${set_id}`).then((response) => fetch(`/api/subs/${username}/sets/${set_id}`).then((response) =>
response.json() response.json(),
); );
type FormOrderSetOrderAddOn = Omit<OrderSetOrderAddOn, "id"> & { type FormOrderSetOrderAddOn = Omit<OrderSetOrderAddOn, "id"> & {
@ -48,11 +57,14 @@ export const SubOrderSet: React.FC = () => {
mode: "uncontrolled", mode: "uncontrolled",
initialValues: { id, name, orders }, initialValues: { id, name, orders },
validate: { validate: {
name: (value) => (value.length < 1 ? "Please enter a name" : null), name: (value: string) =>
value.length < 1 ? "Please enter a name" : null,
orders: { orders: {
name: (value) => (value.length < 1 ? "Please enter a name" : null), name: (value: string) =>
value.length < 1 ? "Please enter a name" : null,
add_ons: { add_ons: {
name: (value) => (value.length < 1 ? "Please enter a name" : null), name: (value: string) =>
value.length < 1 ? "Please enter a name" : null,
}, },
}, },
}, },
@ -68,13 +80,23 @@ export const SubOrderSet: React.FC = () => {
body: JSON.stringify(values), body: JSON.stringify(values),
}).then((response) => { }).then((response) => {
if (response.ok) { if (response.ok) {
notifications.show({
title: "Success",
message: `Updates to ${values.name} were successful`,
color: "green",
});
navigate(`/dashboard/subs/${username}`); navigate(`/dashboard/subs/${username}`);
} else { } else {
notifications.show({
title: "Error",
message: "There was a problem with your request",
color: "red",
});
console.error(response.statusText); console.error(response.statusText);
} }
}); });
}), }),
[form] [form],
); );
const handleNewOrder = React.useCallback(() => { const handleNewOrder = React.useCallback(() => {
@ -97,7 +119,7 @@ export const SubOrderSet: React.FC = () => {
form.setFieldValue(`orders.${idx}._delete`, true); form.setFieldValue(`orders.${idx}._delete`, true);
} }
}, },
[form] [form],
); );
const handleNewAddOn = React.useCallback( const handleNewAddOn = React.useCallback(
@ -108,7 +130,7 @@ export const SubOrderSet: React.FC = () => {
probability: 0.5, probability: 0.5,
}); });
}, },
[form] [form],
); );
const handleRemoveAddOn = React.useCallback( const handleRemoveAddOn = React.useCallback(
@ -123,83 +145,94 @@ export const SubOrderSet: React.FC = () => {
form.setFieldValue(`orders.${idx}.add_ons.${add_on_idx}._delete`, true); form.setFieldValue(`orders.${idx}.add_ons.${add_on_idx}._delete`, true);
} }
}, },
[form] [form],
); );
return ( return (
<form onSubmit={handleSubmit}> <Container pb="xl">
<Container pb="xl"> <Box mb="lg">
<h1>{name}</h1> <Title order={1}>{name}</Title>
<> <Link to={`/dashboard/subs/${username}`}>Return to {username}</Link>
<TextInput {...form.getInputProps("name")} label="Name" /> </Box>
{form.getValues().orders.map(({ id: order_id, _delete }, idx) => <form id="order-set" onSubmit={handleSubmit}>
_delete ? null : ( <TextInput {...form.getInputProps("name")} label="Name" />
<Paper key={order_id} bg="gray.2"> <Title order={2} mt="lg" mb="sm">
<Flex gap="xl" justify="space-between"> Orders
<TextInput </Title>
{...form.getInputProps(`orders.${idx}.name`)} {form.getValues().orders.map(({ id: order_id, _delete }, idx) =>
label="Name" _delete ? null : (
flex={1} <Paper key={order_id} bg="gray.2">
/> <Flex gap="xl" justify="space-between">
<ActionIcon <TextInput
color="red.8" {...form.getInputProps(`orders.${idx}.name`)}
onClick={() => handleRemoveOrder(idx)} label="Name"
> flex={1}
<IconMinus /> />
</ActionIcon> <ActionIcon
</Flex> color="red.8"
<Input.Wrapper onClick={() => handleRemoveOrder(idx)}
label="Weight"
description="Chance this order will be selected"
> >
<Slider <IconMinus />
{...form.getInputProps(`orders.${idx}.weight`)} </ActionIcon>
domain={[0, 100]} </Flex>
/> <Input.Wrapper
</Input.Wrapper> label="Weight"
<Input.Wrapper description="Chance this order will be selected"
label="Repeat" >
description="Percent chance that this order will repeat the next day" <Slider
> {...form.getInputProps(`orders.${idx}.weight`)}
<Slider domain={[0, 100]}
{...form.getInputProps(`orders.${idx}.repeat`)} />
domain={[0, 1]} </Input.Wrapper>
step={0.05} <Input.Wrapper
/> label="Repeat"
</Input.Wrapper> description="Percent chance that this order will repeat the next day"
<h4>Add-On</h4> >
<Slider
{...form.getInputProps(`orders.${idx}.repeat`)}
domain={[0, 1]}
step={0.05}
/>
</Input.Wrapper>
<Paper p="xs">
<Title order={4} mb="sm">
Add-Ons
</Title>
{form {form
.getValues() .getValues()
.orders[idx].add_ons.map( .orders[
({ id: add_on_id, _delete: _add_on_delete }, add_on_idx) => idx
_add_on_delete ? null : ( ].add_ons.map(({ id: add_on_id, _delete: _add_on_delete }, add_on_idx) =>
<Paper key={add_on_id} p="xs"> _add_on_delete ? null : (
<Flex gap="xl" justify="space-between"> <Flex
<TextInput key={add_on_id}
{...form.getInputProps( gap="xl"
`orders.${idx}.add_ons.${add_on_idx}.name` align="center"
)} justify="space-between"
label="Name" >
/> <TextInput
<ActionIcon {...form.getInputProps(
color="red.8" `orders.${idx}.add_ons.${add_on_idx}.name`,
onClick={() => handleRemoveAddOn(idx, add_on_idx)} )}
> />
<IconMinus /> <Input.Wrapper flex={1}>
</ActionIcon> <Slider
</Flex> {...form.getInputProps(
<Input.Wrapper label="Probability"> `orders.${idx}.add_ons.${add_on_idx}.probability`,
<Slider )}
{...form.getInputProps( domain={[0, 1]}
`orders.${idx}.add_ons.${add_on_idx}.probability` step={0.05}
)} />
name="Probability" </Input.Wrapper>
domain={[0, 1]} <ActionIcon
step={0.05} color="red.8"
/> mt="-8px"
</Input.Wrapper> onClick={() => handleRemoveAddOn(idx, add_on_idx)}
</Paper> >
) <IconMinus />
</ActionIcon>
</Flex>
),
)} )}
<Button onClick={() => handleNewAddOn(idx)}> <Button onClick={() => handleNewAddOn(idx)}>
<IconPlus <IconPlus
@ -209,18 +242,26 @@ export const SubOrderSet: React.FC = () => {
New Add On New Add On
</Button> </Button>
</Paper> </Paper>
) </Paper>
)} ),
<Button onClick={handleNewOrder}> )}
<IconPlus style={{ width: "60%", height: "60%" }} stroke={2} /> <Button onClick={handleNewOrder}>
New Order <IconPlus style={{ width: "60%", height: "60%" }} stroke={2} />
</Button> New Order
<Divider my="md" /> </Button>
<Flex justify="flex-end"> <Box my="40px" />
<Button type="submit">Save</Button> <Affix position={{ bottom: 0 }} w="100%">
</Flex> <Paper mb={0} bg="gray.1">
</> <Container>
</Container> <Flex justify="flex-end">
</form> <Button type="submit" form="order-set">
Save
</Button>
</Flex>
</Container>
</Paper>
</Affix>
</form>
</Container>
); );
}; };

View file

@ -1,11 +1,4 @@
import { import { Container, Title } from "@mantine/core";
Container,
Accordion,
TextInput,
Divider,
Input,
Slider,
} from "@mantine/core";
import React from "react"; import React from "react";
import { Link, Params, useLoaderData, useParams } from "react-router"; import { Link, Params, useLoaderData, useParams } from "react-router";
@ -27,16 +20,16 @@ export const SubOrderSets: React.FC = () => {
return ( return (
<Container> <Container>
<h1>Orders for {sub_username}</h1> <Title order={1} mb="lg">
<Accordion> Orders for {sub_username}
{orders </Title>
? orders.map(({ id, name }) => ( {orders
<Link key={id} to={`/dashboard/subs/${sub_username}/${id}`}> ? orders.map(({ id, name }) => (
{name} <Link key={id} to={`/dashboard/subs/${sub_username}/${id}`}>
</Link> {name}
)) </Link>
: null} ))
</Accordion> : null}
</Container> </Container>
); );
}; };

View file

@ -9,8 +9,10 @@ import {
Paper, Paper,
Slider, Slider,
} from "@mantine/core"; } from "@mantine/core";
import { Notifications } from "@mantine/notifications";
import "@mantine/core/styles.css"; import "@mantine/core/styles.css";
import "@mantine/notifications/styles.css";
import { SubsList, subsListLoader } from "./SubsList"; import { SubsList, subsListLoader } from "./SubsList";
import { SubOrderSets, subOrderSetsLoader } from "./SubOrderSets"; import { SubOrderSets, subOrderSetsLoader } from "./SubOrderSets";
@ -37,7 +39,7 @@ const theme = createTheme({
root: { root: {
"--slider-track-bg": theme.colors.gray[4], "--slider-track-bg": theme.colors.gray[4],
}, },
} as any), }) as any,
}), }),
}, },
}); });
@ -63,7 +65,8 @@ const router = createBrowserRouter([
ReactDOM.createRoot(document.getElementById("root")!).render( ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode> <React.StrictMode>
<MantineProvider theme={theme}> <MantineProvider theme={theme}>
<Notifications />
<RouterProvider router={router} /> <RouterProvider router={router} />
</MantineProvider> </MantineProvider>
</React.StrictMode> </React.StrictMode>,
); );