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
|
|
|
);
|
|
|
|
|
};
|