gear-orders/flask/vite/src/SubOrderSet.tsx

268 lines
7.6 KiB
TypeScript
Raw Normal View History

2026-01-29 21:30:51 +00:00
import {
Container,
Paper,
Input,
Slider,
TextInput,
Button,
Flex,
ActionIcon,
2026-01-29 22:23:20 +00:00
Title,
Box,
Affix,
2026-01-29 21:30:51 +00:00
} from "@mantine/core";
2026-01-29 22:23:20 +00:00
import { notifications } from "@mantine/notifications";
2026-01-29 21:30:51 +00:00
import { randomId } from "@mantine/hooks";
import { useForm } from "@mantine/form";
import React from "react";
2026-01-29 22:23:20 +00:00
import {
Params,
useLoaderData,
useParams,
useNavigate,
Link,
} from "react-router";
2026-01-29 21:30:51 +00:00
import { IconMinus, IconPlus } from "@tabler/icons-react";
export const subOrderSetLoader = async ({
params: { username, set_id },
}: {
params: Params<string>;
}) =>
fetch(`/api/subs/${username}/sets/${set_id}`).then((response) =>
2026-01-29 22:23:20 +00:00
response.json(),
2026-01-29 21:30:51 +00:00
);
type FormOrderSetOrderAddOn = Omit<OrderSetOrderAddOn, "id"> & {
id: number | string;
_delete?: boolean;
};
type FormOrderSetOrder = Omit<OrderSetOrder, "id" | "add_ons"> & {
id: number | string;
_delete?: boolean;
add_ons: FormOrderSetOrderAddOn[];
};
type FormOrderSet = Omit<OrderSet, "orders"> & {
orders: FormOrderSetOrder[];
};
export const SubOrderSet: React.FC = () => {
const navigate = useNavigate();
const { username, set_id } = useParams();
const { id, name, orders } = useLoaderData<OrderSet>();
const form = useForm<FormOrderSet>({
mode: "uncontrolled",
initialValues: { id, name, orders },
validate: {
2026-01-29 22:23:20 +00:00
name: (value: string) =>
value.length < 1 ? "Please enter a name" : null,
2026-01-29 21:30:51 +00:00
orders: {
2026-01-29 22:23:20 +00:00
name: (value: string) =>
value.length < 1 ? "Please enter a name" : null,
2026-01-29 21:30:51 +00:00
add_ons: {
2026-01-29 22:23:20 +00:00
name: (value: string) =>
value.length < 1 ? "Please enter a name" : null,
2026-01-29 21:30:51 +00:00
},
},
},
});
const handleSubmit = React.useCallback(
form.onSubmit((values) => {
fetch(`/api/subs/${username}/sets/${set_id}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(values),
}).then((response) => {
if (response.ok) {
2026-01-29 22:23:20 +00:00
notifications.show({
title: "Success",
message: `Updates to ${values.name} were successful`,
color: "green",
});
2026-01-29 21:30:51 +00:00
navigate(`/dashboard/subs/${username}`);
} else {
2026-01-29 22:23:20 +00:00
notifications.show({
title: "Error",
message: "There was a problem with your request",
color: "red",
});
2026-01-29 21:30:51 +00:00
console.error(response.statusText);
}
});
}),
2026-01-29 22:23:20 +00:00
[form],
2026-01-29 21:30:51 +00:00
);
const handleNewOrder = React.useCallback(() => {
form.insertListItem("orders", {
id: `new_${randomId()}`,
name: "",
weight: 10,
repeat: 0.8,
add_ons: [],
});
}, [form]);
const handleRemoveOrder = React.useCallback(
(idx: number) => {
const orderId = form.getValues().orders[idx].id.toString();
if (orderId.indexOf("new_") === 0) {
form.removeListItem("orders", idx);
} else {
form.setFieldValue(`orders.${idx}._delete`, true);
}
},
2026-01-29 22:23:20 +00:00
[form],
2026-01-29 21:30:51 +00:00
);
const handleNewAddOn = React.useCallback(
(idx: number) => {
form.insertListItem(`orders.${idx}.add_ons`, {
id: `new_${randomId()}`,
name: "",
probability: 0.5,
});
},
2026-01-29 22:23:20 +00:00
[form],
2026-01-29 21:30:51 +00:00
);
const handleRemoveAddOn = React.useCallback(
(idx: number, add_on_idx: number) => {
const addOnId = form
.getValues()
.orders[idx].add_ons[add_on_idx].id.toString();
if (addOnId.indexOf("new_") === 0) {
form.removeListItem(`orders.${idx}.add_ons`, add_on_idx);
} else {
form.setFieldValue(`orders.${idx}.add_ons.${add_on_idx}._delete`, true);
}
},
2026-01-29 22:23:20 +00:00
[form],
2026-01-29 21:30:51 +00:00
);
return (
2026-01-29 22:23:20 +00:00
<Container pb="xl">
<Box mb="lg">
<Title order={1}>{name}</Title>
<Link to={`/dashboard/subs/${username}`}>Return to {username}</Link>
</Box>
<form id="order-set" onSubmit={handleSubmit}>
<TextInput {...form.getInputProps("name")} label="Name" />
<Title order={2} mt="lg" mb="sm">
Orders
</Title>
{form.getValues().orders.map(({ id: order_id, _delete }, idx) =>
_delete ? null : (
<Paper key={order_id} bg="gray.2">
<Flex gap="xl" justify="space-between">
<TextInput
{...form.getInputProps(`orders.${idx}.name`)}
label="Name"
flex={1}
/>
<ActionIcon
color="red.8"
onClick={() => handleRemoveOrder(idx)}
2026-01-29 21:30:51 +00:00
>
2026-01-29 22:23:20 +00:00
<IconMinus />
</ActionIcon>
</Flex>
<Input.Wrapper
label="Weight"
description="Chance this order will be selected"
>
<Slider
{...form.getInputProps(`orders.${idx}.weight`)}
domain={[0, 100]}
/>
</Input.Wrapper>
<Input.Wrapper
label="Repeat"
description="Percent chance that this order will repeat the next day"
>
<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>
2026-01-29 21:30:51 +00:00
{form
.getValues()
2026-01-29 22:23:20 +00:00
.orders[
idx
].add_ons.map(({ id: add_on_id, _delete: _add_on_delete }, add_on_idx) =>
_add_on_delete ? null : (
<Flex
key={add_on_id}
gap="xl"
align="center"
justify="space-between"
>
<TextInput
{...form.getInputProps(
`orders.${idx}.add_ons.${add_on_idx}.name`,
)}
/>
<Input.Wrapper flex={1}>
<Slider
{...form.getInputProps(
`orders.${idx}.add_ons.${add_on_idx}.probability`,
)}
domain={[0, 1]}
step={0.05}
/>
</Input.Wrapper>
<ActionIcon
color="red.8"
mt="-8px"
onClick={() => handleRemoveAddOn(idx, add_on_idx)}
>
<IconMinus />
</ActionIcon>
</Flex>
),
2026-01-29 21:30:51 +00:00
)}
<Button onClick={() => handleNewAddOn(idx)}>
<IconPlus
style={{ width: "60%", height: "60%" }}
stroke={2}
/>
New Add On
</Button>
</Paper>
2026-01-29 22:23:20 +00:00
</Paper>
),
)}
<Button onClick={handleNewOrder}>
<IconPlus style={{ width: "60%", height: "60%" }} stroke={2} />
New Order
</Button>
<Box my="40px" />
<Affix position={{ bottom: 0 }} w="100%">
<Paper mb={0} bg="gray.1">
<Container>
<Flex justify="flex-end">
<Button type="submit" form="order-set">
Save
</Button>
</Flex>
</Container>
</Paper>
</Affix>
</form>
</Container>
2026-01-29 21:30:51 +00:00
);
};