Add setting to toggle rounding of prices

This commit is contained in:
Erik Michelson
2024-12-30 01:07:38 +01:00
parent 276fd1e733
commit e93f89aa51
8 changed files with 59 additions and 46 deletions

View File

@@ -316,6 +316,10 @@
"base_url": { "base_url": {
"label": "Base URL", "label": "Base URL",
"tooltip": "The base URL to use when generating features such as QR codes." "tooltip": "The base URL to use when generating features such as QR codes."
},
"round_prices": {
"label": "Round prices",
"tooltip": "Round prices to the nearest whole number."
} }
}, },
"extra_fields": { "extra_fields": {

View File

@@ -26,7 +26,7 @@ import {
import { removeUndefined } from "../../utils/filtering"; import { removeUndefined } from "../../utils/filtering";
import { EntityType, useGetFields } from "../../utils/queryFields"; import { EntityType, useGetFields } from "../../utils/queryFields";
import { TableState, useInitialTableState, useStoreInitialState } from "../../utils/saveload"; import { TableState, useInitialTableState, useStoreInitialState } from "../../utils/saveload";
import { useCurrency } from "../../utils/settings"; import { useCurrencyFormatter } from "../../utils/settings";
import { IFilament } from "./model"; import { IFilament } from "./model";
dayjs.extend(utc); dayjs.extend(utc);
@@ -77,7 +77,7 @@ export const FilamentList: React.FC<IResourceComponentsProps> = () => {
const invalidate = useInvalidate(); const invalidate = useInvalidate();
const navigate = useNavigate(); const navigate = useNavigate();
const extraFields = useGetFields(EntityType.filament); const extraFields = useGetFields(EntityType.filament);
const currency = useCurrency(); const currencyFormatter = useCurrencyFormatter();
const allColumnsWithExtraFields = [...allColumns, ...(extraFields.data?.map((field) => "extra." + field.key) ?? [])]; const allColumnsWithExtraFields = [...allColumns, ...(extraFields.data?.map((field) => "extra." + field.key) ?? [])];
@@ -263,12 +263,10 @@ export const FilamentList: React.FC<IResourceComponentsProps> = () => {
align: "right", align: "right",
width: 80, width: 80,
render: (_, obj: IFilamentCollapsed) => { render: (_, obj: IFilamentCollapsed) => {
return obj.price?.toLocaleString(undefined, { if (obj.price === undefined) {
style: "currency", return "";
currencyDisplay: "narrowSymbol", }
currency: currency, return currencyFormatter.format(obj.price);
notation: "compact",
});
}, },
}), }),
NumberColumn({ NumberColumn({

View File

@@ -10,7 +10,7 @@ import { NumberFieldUnit } from "../../components/numberField";
import SpoolIcon from "../../components/spoolIcon"; import SpoolIcon from "../../components/spoolIcon";
import { enrichText } from "../../utils/parsing"; import { enrichText } from "../../utils/parsing";
import { EntityType, useGetFields } from "../../utils/queryFields"; import { EntityType, useGetFields } from "../../utils/queryFields";
import { useCurrency } from "../../utils/settings"; import { useCurrencyFormatter } from "../../utils/settings";
import { IFilament } from "./model"; import { IFilament } from "./model";
dayjs.extend(utc); dayjs.extend(utc);
@@ -20,7 +20,7 @@ export const FilamentShow: React.FC<IResourceComponentsProps> = () => {
const t = useTranslate(); const t = useTranslate();
const navigate = useNavigate(); const navigate = useNavigate();
const extraFields = useGetFields(EntityType.filament); const extraFields = useGetFields(EntityType.filament);
const currency = useCurrency(); const currencyFormatter = useCurrencyFormatter();
const { queryResult } = useShow<IFilament>({ const { queryResult } = useShow<IFilament>({
liveMode: "auto", liveMode: "auto",
}); });
@@ -92,15 +92,7 @@ export const FilamentShow: React.FC<IResourceComponentsProps> = () => {
<Title level={5}>{t("filament.fields.material")}</Title> <Title level={5}>{t("filament.fields.material")}</Title>
<TextField value={record?.material} /> <TextField value={record?.material} />
<Title level={5}>{t("filament.fields.price")}</Title> <Title level={5}>{t("filament.fields.price")}</Title>
<NumberField <TextField value={record?.price ? currencyFormatter.format(record.price) : ""} />
value={record?.price ?? ""}
options={{
style: "currency",
currency: currency,
currencyDisplay: "narrowSymbol",
notation: "compact",
}}
/>
<Title level={5}>{t("filament.fields.density")}</Title> <Title level={5}>{t("filament.fields.density")}</Title>
<NumberFieldUnit <NumberFieldUnit
value={record?.density ?? ""} value={record?.density ?? ""}

View File

@@ -1,5 +1,5 @@
import { useTranslate } from "@refinedev/core"; import { useTranslate } from "@refinedev/core";
import { Button, Form, Input, message } from "antd"; import { Button, Checkbox, Form, Input, message } from "antd";
import { useEffect } from "react"; import { useEffect } from "react";
import { useGetSettings, useSetSetting } from "../../utils/querySettings"; import { useGetSettings, useSetSetting } from "../../utils/querySettings";
@@ -7,6 +7,7 @@ export function GeneralSettings() {
const settings = useGetSettings(); const settings = useGetSettings();
const setBaseUrl = useSetSetting("base_url"); const setBaseUrl = useSetSetting("base_url");
const setCurrency = useSetSetting("currency"); const setCurrency = useSetSetting("currency");
const setRoundPrices = useSetSetting("round_prices");
const [form] = Form.useForm(); const [form] = Form.useForm();
const [messageApi, contextHolder] = message.useMessage(); const [messageApi, contextHolder] = message.useMessage();
const t = useTranslate(); const t = useTranslate();
@@ -17,6 +18,7 @@ export function GeneralSettings() {
form.setFieldsValue({ form.setFieldsValue({
currency: JSON.parse(settings.data.currency.value), currency: JSON.parse(settings.data.currency.value),
base_url: JSON.parse(settings.data.base_url.value), base_url: JSON.parse(settings.data.base_url.value),
round_prices: JSON.parse(settings.data.round_prices.value),
}); });
} }
}, [settings.data, form]); }, [settings.data, form]);
@@ -29,7 +31,7 @@ export function GeneralSettings() {
}, [setCurrency.isSuccess, messageApi, t]); }, [setCurrency.isSuccess, messageApi, t]);
// Handle form submit // Handle form submit
const onFinish = (values: { currency: string; base_url: string }) => { const onFinish = (values: { currency: string; base_url: string, round_prices: boolean }) => {
// Check if the currency has changed // Check if the currency has changed
if (settings.data?.currency.value !== JSON.stringify(values.currency)) { if (settings.data?.currency.value !== JSON.stringify(values.currency)) {
setCurrency.mutate(values.currency); setCurrency.mutate(values.currency);
@@ -38,6 +40,11 @@ export function GeneralSettings() {
if (settings.data?.base_url.value !== JSON.stringify(values.base_url)) { if (settings.data?.base_url.value !== JSON.stringify(values.base_url)) {
setBaseUrl.mutate(values.base_url); setBaseUrl.mutate(values.base_url);
} }
// Check if the setting to round prices has changed
if (settings.data?.round_prices.value !== JSON.stringify(values.round_prices)) {
setRoundPrices.mutate(values.round_prices);
}
}; };
return ( return (
@@ -48,6 +55,7 @@ export function GeneralSettings() {
wrapperCol={{ span: 16 }} wrapperCol={{ span: 16 }}
initialValues={{ initialValues={{
currency: settings.data?.currency.value, currency: settings.data?.currency.value,
round_prices: settings.data?.round_prices.value,
}} }}
onFinish={onFinish} onFinish={onFinish}
style={{ style={{
@@ -86,6 +94,15 @@ export function GeneralSettings() {
<Input placeholder="https://example.com:8000" /> <Input placeholder="https://example.com:8000" />
</Form.Item> </Form.Item>
<Form.Item
label={t("settings.general.round_prices.label")}
tooltip={t("settings.general.round_prices.tooltip")}
name="round_prices"
valuePropName="checked"
>
<Checkbox />
</Form.Item>
<Form.Item wrapperCol={{ offset: 8, span: 16 }}> <Form.Item wrapperCol={{ offset: 8, span: 16 }}>
<Button type="primary" htmlType="submit" loading={settings.isFetching || setCurrency.isLoading}> <Button type="primary" htmlType="submit" loading={settings.isFetching || setCurrency.isLoading}>
{t("buttons.save")} {t("buttons.save")}

View File

@@ -36,7 +36,7 @@ import {
import { removeUndefined } from "../../utils/filtering"; import { removeUndefined } from "../../utils/filtering";
import { EntityType, useGetFields } from "../../utils/queryFields"; import { EntityType, useGetFields } from "../../utils/queryFields";
import { TableState, useInitialTableState, useSavedState, useStoreInitialState } from "../../utils/saveload"; import { TableState, useInitialTableState, useSavedState, useStoreInitialState } from "../../utils/saveload";
import { useCurrency } from "../../utils/settings"; import { useCurrencyFormatter } from "../../utils/settings";
import { setSpoolArchived, useSpoolAdjustModal } from "./functions"; import { setSpoolArchived, useSpoolAdjustModal } from "./functions";
import { ISpool } from "./model"; import { ISpool } from "./model";
@@ -102,7 +102,7 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
const invalidate = useInvalidate(); const invalidate = useInvalidate();
const navigate = useNavigate(); const navigate = useNavigate();
const extraFields = useGetFields(EntityType.spool); const extraFields = useGetFields(EntityType.spool);
const currency = useCurrency(); const currencyFormatter = useCurrencyFormatter();
const { openSpoolAdjustModal, spoolAdjustModal } = useSpoolAdjustModal(); const { openSpoolAdjustModal, spoolAdjustModal } = useSpoolAdjustModal();
const allColumnsWithExtraFields = [...allColumns, ...(extraFields.data?.map((field) => "extra." + field.key) ?? [])]; const allColumnsWithExtraFields = [...allColumns, ...(extraFields.data?.map((field) => "extra." + field.key) ?? [])];
@@ -381,12 +381,10 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
align: "right", align: "right",
width: 80, width: 80,
render: (_, obj: ISpoolCollapsed) => { render: (_, obj: ISpoolCollapsed) => {
return obj.price?.toLocaleString(undefined, { if (obj.price === undefined) {
style: "currency", return "";
currencyDisplay: "narrowSymbol", }
currency: currency, return currencyFormatter.format(obj.price);
notation: "compact",
});
}, },
}), }),
NumberColumn({ NumberColumn({

View File

@@ -10,7 +10,7 @@ import { NumberFieldUnit } from "../../components/numberField";
import SpoolIcon from "../../components/spoolIcon"; import SpoolIcon from "../../components/spoolIcon";
import { enrichText } from "../../utils/parsing"; import { enrichText } from "../../utils/parsing";
import { EntityType, useGetFields } from "../../utils/queryFields"; import { EntityType, useGetFields } from "../../utils/queryFields";
import { useCurrency } from "../../utils/settings"; import { useCurrencyFormatter } from "../../utils/settings";
import { IFilament } from "../filaments/model"; import { IFilament } from "../filaments/model";
import { setSpoolArchived } from "./functions"; import { setSpoolArchived } from "./functions";
import { ISpool } from "./model"; import { ISpool } from "./model";
@@ -23,7 +23,7 @@ const { confirm } = Modal;
export const SpoolShow: React.FC<IResourceComponentsProps> = () => { export const SpoolShow: React.FC<IResourceComponentsProps> = () => {
const t = useTranslate(); const t = useTranslate();
const extraFields = useGetFields(EntityType.spool); const extraFields = useGetFields(EntityType.spool);
const currency = useCurrency(); const currencyFormatter = useCurrencyFormatter();
const invalidate = useInvalidate(); const invalidate = useInvalidate();
const { queryResult } = useShow<ISpool>({ const { queryResult } = useShow<ISpool>({
@@ -33,13 +33,12 @@ export const SpoolShow: React.FC<IResourceComponentsProps> = () => {
const record = data?.data; const record = data?.data;
const spoolPrice = (item: ISpool) => { const spoolPrice = (item?: ISpool) => {
let spoolPrice = ""; const price = item?.price ?? item?.filament.price;
if (item.price === undefined) { if (price === undefined) {
spoolPrice = `${item.filament.price}`; return "";
return spoolPrice;
} }
return item.price; return currencyFormatter.format(price);
}; };
// Function for opening an ant design modal that asks for confirmation for archiving a spool // Function for opening an ant design modal that asks for confirmation for archiving a spool
@@ -141,15 +140,7 @@ export const SpoolShow: React.FC<IResourceComponentsProps> = () => {
<Title level={5}>{t("spool.fields.filament")}</Title> <Title level={5}>{t("spool.fields.filament")}</Title>
{colorObj && <SpoolIcon color={colorObj} />} <TextField value={record ? filamentURL(record?.filament) : ""} /> {colorObj && <SpoolIcon color={colorObj} />} <TextField value={record ? filamentURL(record?.filament) : ""} />
<Title level={5}>{t("spool.fields.price")}</Title> <Title level={5}>{t("spool.fields.price")}</Title>
<NumberField <TextField value={spoolPrice(record)} />
value={record ? spoolPrice(record) : ""}
options={{
style: "currency",
currency: currency,
currencyDisplay: "narrowSymbol",
notation: "compact",
}}
/>
<Title level={5}>{t("spool.fields.registered")}</Title> <Title level={5}>{t("spool.fields.registered")}</Title>
<DateField <DateField
value={dayjs.utc(record?.registered).local()} value={dayjs.utc(record?.registered).local()}

View File

@@ -17,3 +17,15 @@ export function getCurrencySymbol(locale: string | undefined, currency: string)
.replace(/\d/g, "") .replace(/\d/g, "")
.trim(); .trim();
} }
export function useCurrencyFormatter() {
const currency = useCurrency();
const roundPrices = JSON.parse(useGetSetting("round_prices").data?.value ?? "false");
return new Intl.NumberFormat(undefined, {
style: "currency",
currency: currency,
currencyDisplay: "narrowSymbol",
notation: roundPrices ? "compact" : "standard",
});
}

View File

@@ -62,6 +62,7 @@ def parse_setting(key: str) -> SettingDefinition:
register_setting("currency", SettingType.STRING, json.dumps("EUR")) register_setting("currency", SettingType.STRING, json.dumps("EUR"))
register_setting("round_prices", SettingType.BOOLEAN, json.dumps(obj=False))
register_setting("print_presets", SettingType.ARRAY, json.dumps([])) register_setting("print_presets", SettingType.ARRAY, json.dumps([]))
register_setting("extra_fields_vendor", SettingType.ARRAY, json.dumps([])) register_setting("extra_fields_vendor", SettingType.ARRAY, json.dumps([]))