Currency setting is now utilized in filament/spool lists, edit pages etc

This commit is contained in:
Donkie
2024-02-02 23:06:06 +01:00
parent 220457c1ac
commit c65bc4201b
11 changed files with 96 additions and 8 deletions

View File

@@ -153,7 +153,7 @@
"archived": "Archived" "archived": "Archived"
}, },
"fields_help": { "fields_help": {
"price": "Price of a full spool in the system configured currency. If not set, the price of the filament will be assumed instead.", "price": "Price of a full spool. If not set, the price of the filament will be assumed instead.",
"weight_to_use": "Select what weight value to enter. Measured weight is only available if the spool weight is set for the selected filament.", "weight_to_use": "Select what weight value to enter. Measured weight is only available if the spool weight is set for the selected filament.",
"used_weight": "How much filament has been used from the spool. A new spool should have 0g used.", "used_weight": "How much filament has been used from the spool. A new spool should have 0g used.",
"remaining_weight": "How much filament is left on the spool. For a new spool this should match the spool weight.", "remaining_weight": "How much filament is left on the spool. For a new spool this should match the spool weight.",
@@ -202,7 +202,7 @@
"fields_help": { "fields_help": {
"name": "Filament name, to distinguish this filament type among others from the same vendor. Should contain the color for example.", "name": "Filament name, to distinguish this filament type among others from the same vendor. Should contain the color for example.",
"material": "E.g. PLA, ABS, PETG, etc.", "material": "E.g. PLA, ABS, PETG, etc.",
"price": "Price of a full spool in the system configured currency.", "price": "Price of a full spool.",
"weight": "The filament weight of a full spool (net weight). This should not include the weight of the spool itself, only the filament. It is what is usually written on the packaging.", "weight": "The filament weight of a full spool (net weight). This should not include the weight of the spool itself, only the filament. It is what is usually written on the packaging.",
"spool_weight": "The weight of an empty spool. Used to determine measured weight of a spool.", "spool_weight": "The weight of an empty spool. Used to determine measured weight of a spool.",
"article_number": "E.g. EAN, UPC, etc." "article_number": "E.g. EAN, UPC, etc."

View File

@@ -53,6 +53,7 @@ interface BaseColumnProps<Obj extends Entity> {
width?: number; width?: number;
actions?: (record: Obj) => Action[]; actions?: (record: Obj) => Action[];
transform?: (value: unknown) => unknown; transform?: (value: unknown) => unknown;
render?: (rawValue: string | undefined, record: Obj) => React.ReactNode;
} }
interface FilteredColumnProps { interface FilteredColumnProps {

View File

@@ -10,6 +10,7 @@ import { EntityType, useGetFields } from "../../utils/queryFields";
import { ExtraFieldFormItem, StringifiedExtras } from "../../components/extraFields"; import { ExtraFieldFormItem, StringifiedExtras } from "../../components/extraFields";
import dayjs from "dayjs"; import dayjs from "dayjs";
import utc from "dayjs/plugin/utc"; import utc from "dayjs/plugin/utc";
import { getCurrencySymbol, useCurrency } from "../../utils/settings";
dayjs.extend(utc); dayjs.extend(utc);
@@ -20,6 +21,7 @@ interface CreateOrCloneProps {
export const FilamentCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps> = (props) => { export const FilamentCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps> = (props) => {
const t = useTranslate(); const t = useTranslate();
const extraFields = useGetFields(EntityType.filament); const extraFields = useGetFields(EntityType.filament);
const currency = useCurrency();
const { form, formProps, formLoading, onFinish, redirect } = useForm< const { form, formProps, formLoading, onFinish, redirect } = useForm<
IFilament, IFilament,
@@ -149,7 +151,12 @@ export const FilamentCreate: React.FC<IResourceComponentsProps & CreateOrClonePr
}, },
]} ]}
> >
<InputNumber precision={2} formatter={numberFormatter} parser={numberParser} /> <InputNumber
addonAfter={getCurrencySymbol(undefined, currency)}
precision={2}
formatter={numberFormatter}
parser={numberParser}
/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("filament.fields.density")} label={t("filament.fields.density")}

View File

@@ -10,6 +10,7 @@ import { IFilament, IFilamentParsedExtras } from "./model";
import { EntityType, useGetFields } from "../../utils/queryFields"; import { EntityType, useGetFields } from "../../utils/queryFields";
import { ExtraFieldFormItem, StringifiedExtras } from "../../components/extraFields"; import { ExtraFieldFormItem, StringifiedExtras } from "../../components/extraFields";
import { ParsedExtras } from "../../components/extraFields"; import { ParsedExtras } from "../../components/extraFields";
import { getCurrencySymbol, useCurrency } from "../../utils/settings";
/* /*
The API returns the extra fields as JSON values, but we need to parse them into their real types The API returns the extra fields as JSON values, but we need to parse them into their real types
@@ -23,6 +24,7 @@ export const FilamentEdit: React.FC<IResourceComponentsProps> = () => {
const [messageApi, contextHolder] = message.useMessage(); const [messageApi, contextHolder] = message.useMessage();
const [hasChanged, setHasChanged] = useState(false); const [hasChanged, setHasChanged] = useState(false);
const extraFields = useGetFields(EntityType.filament); const extraFields = useGetFields(EntityType.filament);
const currency = useCurrency();
const { formProps, saveButtonProps } = useForm<IFilament, HttpError, IFilament, IFilament>({ const { formProps, saveButtonProps } = useForm<IFilament, HttpError, IFilament, IFilament>({
liveMode: "manual", liveMode: "manual",
@@ -168,7 +170,12 @@ export const FilamentEdit: React.FC<IResourceComponentsProps> = () => {
}, },
]} ]}
> >
<InputNumber precision={2} formatter={numberFormatter} parser={numberParser} /> <InputNumber
addonAfter={getCurrencySymbol(undefined, currency)}
precision={2}
formatter={numberFormatter}
parser={numberParser}
/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("filament.fields.density")} label={t("filament.fields.density")}

View File

@@ -27,6 +27,7 @@ import { useLiveify } from "../../components/liveify";
import { removeUndefined } from "../../utils/filtering"; import { removeUndefined } from "../../utils/filtering";
import { EntityType, useGetFields } from "../../utils/queryFields"; import { EntityType, useGetFields } from "../../utils/queryFields";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useCurrency } from "../../utils/settings";
dayjs.extend(utc); dayjs.extend(utc);
@@ -76,6 +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 allColumnsWithExtraFields = [...allColumns, ...(extraFields.data?.map((field) => "extra." + field.key) ?? [])]; const allColumnsWithExtraFields = [...allColumns, ...(extraFields.data?.map((field) => "extra." + field.key) ?? [])];
@@ -253,6 +255,14 @@ export const FilamentList: React.FC<IResourceComponentsProps> = () => {
id: "price", id: "price",
i18ncat: "filament", i18ncat: "filament",
width: 80, width: 80,
render: (_, obj: IFilamentCollapsed) => {
return obj.price?.toLocaleString(undefined, {
style: "currency",
currencyDisplay: "narrowSymbol",
currency: currency,
notation: "compact",
});
},
}), }),
NumberColumn({ NumberColumn({
...commonProps, ...commonProps,

View File

@@ -10,6 +10,7 @@ import { enrichText } from "../../utils/parsing";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { EntityType, useGetFields } from "../../utils/queryFields"; import { EntityType, useGetFields } from "../../utils/queryFields";
import { ExtraFieldDisplay } from "../../components/extraFields"; import { ExtraFieldDisplay } from "../../components/extraFields";
import { useCurrency } from "../../utils/settings";
dayjs.extend(utc); dayjs.extend(utc);
const { Title } = Typography; const { Title } = Typography;
@@ -18,6 +19,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 { queryResult } = useShow<IFilament>({ const { queryResult } = useShow<IFilament>({
liveMode: "auto", liveMode: "auto",
}); });
@@ -82,7 +84,15 @@ 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 value={record?.price ?? ""} /> <NumberField
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

@@ -13,6 +13,7 @@ import "../../utils/overrides.css";
import { EntityType, useGetFields } from "../../utils/queryFields"; import { EntityType, useGetFields } from "../../utils/queryFields";
import { ExtraFieldFormItem, StringifiedExtras } from "../../components/extraFields"; import { ExtraFieldFormItem, StringifiedExtras } from "../../components/extraFields";
import utc from "dayjs/plugin/utc"; import utc from "dayjs/plugin/utc";
import { getCurrencySymbol, useCurrency } from "../../utils/settings";
dayjs.extend(utc); dayjs.extend(utc);
@@ -23,6 +24,7 @@ interface CreateOrCloneProps {
export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps> = (props) => { export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps> = (props) => {
const t = useTranslate(); const t = useTranslate();
const extraFields = useGetFields(EntityType.spool); const extraFields = useGetFields(EntityType.spool);
const currency = useCurrency();
const { form, formProps, formLoading, onFinish, redirect } = useForm< const { form, formProps, formLoading, onFinish, redirect } = useForm<
ISpool, ISpool,
@@ -245,7 +247,12 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
}, },
]} ]}
> >
<InputNumber precision={2} formatter={numberFormatter} parser={numberParser} /> <InputNumber
addonAfter={getCurrencySymbol(undefined, currency)}
precision={2}
formatter={numberFormatter}
parser={numberParser}
/>
</Form.Item> </Form.Item>
<Form.Item hidden={true} name={["used_weight"]} initialValue={0}> <Form.Item hidden={true} name={["used_weight"]} initialValue={0}>
<InputNumber value={usedWeight} /> <InputNumber value={usedWeight} />

View File

@@ -12,6 +12,7 @@ import { message } from "antd/lib";
import { EntityType, useGetFields } from "../../utils/queryFields"; import { EntityType, useGetFields } from "../../utils/queryFields";
import { ExtraFieldFormItem, StringifiedExtras } from "../../components/extraFields"; import { ExtraFieldFormItem, StringifiedExtras } from "../../components/extraFields";
import { ParsedExtras } from "../../components/extraFields"; import { ParsedExtras } from "../../components/extraFields";
import { getCurrencySymbol, useCurrency } from "../../utils/settings";
/* /*
The API returns the extra fields as JSON values, but we need to parse them into their real types The API returns the extra fields as JSON values, but we need to parse them into their real types
@@ -31,6 +32,7 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
const [messageApi, contextHolder] = message.useMessage(); const [messageApi, contextHolder] = message.useMessage();
const [hasChanged, setHasChanged] = useState(false); const [hasChanged, setHasChanged] = useState(false);
const extraFields = useGetFields(EntityType.spool); const extraFields = useGetFields(EntityType.spool);
const currency = useCurrency();
const { form, formProps, saveButtonProps } = useForm<ISpool, HttpError, ISpool, ISpool>({ const { form, formProps, saveButtonProps } = useForm<ISpool, HttpError, ISpool, ISpool>({
liveMode: "manual", liveMode: "manual",
@@ -229,7 +231,12 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
}, },
]} ]}
> >
<InputNumber precision={2} formatter={numberFormatter} parser={numberParser} /> <InputNumber
addonAfter={getCurrencySymbol(undefined, currency)}
precision={2}
formatter={numberFormatter}
parser={numberParser}
/>
</Form.Item> </Form.Item>
<Form.Item hidden={true} name={["used_weight"]} initialValue={0}> <Form.Item hidden={true} name={["used_weight"]} initialValue={0}>
<InputNumber value={usedWeight} /> <InputNumber value={usedWeight} />

View File

@@ -37,6 +37,7 @@ import { useLiveify } from "../../components/liveify";
import { removeUndefined } from "../../utils/filtering"; import { removeUndefined } from "../../utils/filtering";
import { EntityType, useGetFields } from "../../utils/queryFields"; import { EntityType, useGetFields } from "../../utils/queryFields";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useCurrency } from "../../utils/settings";
dayjs.extend(utc); dayjs.extend(utc);
@@ -100,6 +101,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 allColumnsWithExtraFields = [...allColumns, ...(extraFields.data?.map((field) => "extra." + field.key) ?? [])]; const allColumnsWithExtraFields = [...allColumns, ...(extraFields.data?.map((field) => "extra." + field.key) ?? [])];
@@ -356,6 +358,14 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
id: "price", id: "price",
i18ncat: "spool", i18ncat: "spool",
width: 80, width: 80,
render: (_, obj: ISpoolCollapsed) => {
return obj.price?.toLocaleString(undefined, {
style: "currency",
currencyDisplay: "narrowSymbol",
currency: currency,
notation: "compact",
});
},
}), }),
NumberColumn({ NumberColumn({
...commonProps, ...commonProps,

View File

@@ -10,6 +10,7 @@ import { enrichText } from "../../utils/parsing";
import { IFilament } from "../filaments/model"; import { IFilament } from "../filaments/model";
import { EntityType, useGetFields } from "../../utils/queryFields"; import { EntityType, useGetFields } from "../../utils/queryFields";
import { ExtraFieldDisplay } from "../../components/extraFields"; import { ExtraFieldDisplay } from "../../components/extraFields";
import { useCurrency } from "../../utils/settings";
dayjs.extend(utc); dayjs.extend(utc);
@@ -18,6 +19,7 @@ const { Title } = Typography;
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 { queryResult } = useShow<ISpool>({ const { queryResult } = useShow<ISpool>({
liveMode: "auto", liveMode: "auto",
@@ -71,7 +73,15 @@ export const SpoolShow: React.FC<IResourceComponentsProps> = () => {
<Title level={5}>{t("spool.fields.filament")}</Title> <Title level={5}>{t("spool.fields.filament")}</Title>
<TextField value={record ? filamentURL(record?.filament) : ""} /> <TextField value={record ? filamentURL(record?.filament) : ""} />
<Title level={5}>{t("spool.fields.price")}</Title> <Title level={5}>{t("spool.fields.price")}</Title>
<NumberField value={record ? spoolPrice(record) : ""} /> <NumberField
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

@@ -0,0 +1,19 @@
import { useGetSetting } from "./querySettings";
export function useCurrency() {
const { data: currency } = useGetSetting("currency");
return JSON.parse(currency?.value ?? '"EUR"');
}
export function getCurrencySymbol(locale: string | undefined, currency: string) {
return (0)
.toLocaleString(locale, {
style: "currency",
currency,
currencyDisplay: "narrowSymbol",
minimumFractionDigits: 0,
maximumFractionDigits: 0,
})
.replace(/\d/g, "")
.trim();
}