Orders Pools - Improve UI
This commit is contained in:
parent
257461307e
commit
b194b19a15
5 changed files with 153 additions and 92 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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,15 +72,19 @@ export const OrderSets: React.FC<OrderSetProps> = ({
|
||||||
|
|
||||||
const [isMastodonSet, setIsMastodonSet] = React.useState(true);
|
const [isMastodonSet, setIsMastodonSet] = React.useState(true);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
fetch(`/api/subs/${username}`)
|
if (username) {
|
||||||
.then((response) => response.json())
|
fetch(`/api/subs/${username}`)
|
||||||
.then((data) => {
|
.then((response) => response.json())
|
||||||
if (!data.mastodon_server || !data.mastodon_username) {
|
.then((data) => {
|
||||||
setIsMastodonSet(false);
|
if (!data.mastodon_server || !data.mastodon_username) {
|
||||||
}
|
setIsMastodonSet(false);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}, [username]);
|
}, [username]);
|
||||||
|
|
||||||
|
const [portalRef, setPortalRef] = React.useState<HTMLElement | null>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box mb="lg">
|
<Box mb="lg">
|
||||||
|
|
@ -92,90 +112,123 @@ export const OrderSets: React.FC<OrderSetProps> = ({
|
||||||
</Alert>
|
</Alert>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
<Flex gap="md" wrap="wrap">
|
<div
|
||||||
{orderSets
|
ref={(node) => {
|
||||||
? orderSets.map(
|
if (portalRef == null && node != null) {
|
||||||
({
|
setPortalRef(node);
|
||||||
id,
|
}
|
||||||
name,
|
}}
|
||||||
scheduled,
|
>
|
||||||
orders,
|
<Flex gap="md" wrap="wrap">
|
||||||
time,
|
{orderSets
|
||||||
weekdays,
|
? orderSets.map(
|
||||||
weekends,
|
({
|
||||||
probability,
|
id,
|
||||||
punishment_pool_name,
|
name,
|
||||||
}) => (
|
scheduled,
|
||||||
<Card
|
orders,
|
||||||
key={id}
|
time,
|
||||||
shadow="sm"
|
weekdays,
|
||||||
padding="lg"
|
weekends,
|
||||||
radius="md"
|
probability,
|
||||||
withBorder
|
punishment_pool_name,
|
||||||
bg="gray.2"
|
}) => (
|
||||||
w="400px"
|
<Card
|
||||||
>
|
key={id}
|
||||||
<Flex direction="column" gap="md" h="100%">
|
shadow="sm"
|
||||||
<Title order={4}>{name}</Title>
|
padding="lg"
|
||||||
{scheduled ? (
|
radius="md"
|
||||||
<>
|
withBorder
|
||||||
<Flex gap="xs">
|
bg="gray.2"
|
||||||
<Text>Scheduled:</Text>
|
w="400px"
|
||||||
{time.split(",").map((time) => (
|
mb="0"
|
||||||
<div key={time}>
|
>
|
||||||
<TimeValue key={time} value={time} format="12h" />
|
<Flex direction="column" gap="md" h="100%">
|
||||||
</div>
|
<Title order={4}>{name}</Title>
|
||||||
))}
|
{scheduled ? (
|
||||||
</Flex>
|
<>
|
||||||
<Flex gap="md" align="center">
|
<Flex gap="xs">
|
||||||
{weekdays ? (
|
<Text>
|
||||||
<Badge color="blue">Weekdays</Badge>
|
<b>Scheduled:</b>
|
||||||
) : null}
|
</Text>
|
||||||
{weekends ? (
|
{time.split(",").map((time) => (
|
||||||
<Badge color="blue">Weekends</Badge>
|
<div key={time}>
|
||||||
) : null}
|
<TimeValue
|
||||||
<RingProgress
|
key={time}
|
||||||
size={80}
|
value={time}
|
||||||
label={<Text size="xs">{probability * 100}%</Text>}
|
format="12h"
|
||||||
sections={[
|
/>
|
||||||
{ color: "cyan", value: probability * 100 },
|
</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"
|
||||||
/>
|
/>
|
||||||
</Flex>
|
) : null}
|
||||||
</>
|
<ConfirmDialogButton
|
||||||
) : null}
|
buttonColor="red.8"
|
||||||
<Text mb="md" flex={1}>
|
buttonText="Delete"
|
||||||
Orders: {orders.length}
|
text={`Are you sure you want to delete ${name}?`}
|
||||||
<br />
|
onConfirm={() => handleDelete(id)}
|
||||||
{punishment_pool_name
|
>
|
||||||
? `Punishments: ${punishment_pool_name}`
|
<IconTrash />
|
||||||
: null}
|
</ConfirmDialogButton>
|
||||||
</Text>
|
<NavigateButton to={`/orders/${username}/${id}`}>
|
||||||
<Flex gap="md" justify="end">
|
<IconPencil style={{ marginRight: "0.5rem" }} />
|
||||||
<ConfirmDialogButton
|
Edit
|
||||||
buttonColor="red.8"
|
</NavigateButton>
|
||||||
buttonText="Delete"
|
</Flex>
|
||||||
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>
|
||||||
</Flex>
|
</Card>
|
||||||
</Card>
|
),
|
||||||
),
|
)
|
||||||
)
|
: null}
|
||||||
: null}
|
</Flex>
|
||||||
</Flex>
|
</div>
|
||||||
<NavigateButton to={`/orders/${username}/new`}>
|
<Box my="lg">
|
||||||
<IconPlus style={{ marginRight: "0.5rem" }} />
|
<NavigateButton to={`/orders/${username}/new`}>
|
||||||
New
|
<IconPlus style={{ marginRight: "0.5rem" }} />
|
||||||
</NavigateButton>
|
New
|
||||||
<Box mb="lg"></Box>
|
</NavigateButton>
|
||||||
|
</Box>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue