Merge branch 'master' into adjust-spool-button-on-show
This commit is contained in:
@@ -15,12 +15,12 @@ import {
|
||||
UserOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import loadable from "@loadable/component";
|
||||
import routerBindings, { DocumentTitleHandler, UnsavedChangesNotifier } from "@refinedev/react-router-v6";
|
||||
import routerBindings, { DocumentTitleHandler, UnsavedChangesNotifier } from "@refinedev/react-router";
|
||||
import { ConfigProvider } from "antd";
|
||||
import { Locale } from "antd/es/locale";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { BrowserRouter, Outlet, Route, Routes } from "react-router-dom";
|
||||
import { BrowserRouter, Outlet, Route, Routes } from "react-router";
|
||||
import dataProvider from "./components/dataProvider";
|
||||
import { Favicon } from "./components/favicon";
|
||||
import { SpoolmanLayout } from "./components/layout";
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ColumnFilterItem, ColumnType } from "antd/es/table/interface";
|
||||
import dayjs from "dayjs";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { AlignType } from "rc-table/lib/interface";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Link } from "react-router";
|
||||
import { getFiltersForField, typeFilters } from "../utils/filtering";
|
||||
import { enrichText } from "../utils/parsing";
|
||||
import { Field, FieldType } from "../utils/queryFields";
|
||||
@@ -111,11 +111,13 @@ function Column<Obj extends Entity>(
|
||||
if (props.loadingFilters) {
|
||||
columnProps.filterDropdown = <FilterDropdownLoading />;
|
||||
}
|
||||
columnProps.onFilterDropdownOpenChange = (open) => {
|
||||
if (open && props.onFilterDropdownOpen) {
|
||||
props.onFilterDropdownOpen();
|
||||
columnProps.filterDropdownProps = {
|
||||
onOpenChange: (open) => {
|
||||
if (open && props.onFilterDropdownOpen) {
|
||||
props.onFilterDropdownOpen();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
if (props.dataId) {
|
||||
columnProps.key = props.dataId;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { ColorModeContext } from "../../contexts/color-mode";
|
||||
|
||||
import { languages } from "../../i18n";
|
||||
import QRCodeScannerModal from "../qrCodeScanner";
|
||||
import "/node_modules/flag-icons/css/flag-icons.min.css";
|
||||
|
||||
const { useToken } = theme;
|
||||
|
||||
@@ -22,7 +21,6 @@ export const Header: React.FC<RefineThemedLayoutV2HeaderProps> = ({ sticky }) =>
|
||||
const menuItems: MenuProps["items"] = [...(Object.keys(languages) || [])].sort().map((lang: string) => ({
|
||||
key: lang,
|
||||
onClick: () => changeLanguage(lang),
|
||||
icon: <span className={"fi fi-" + languages[lang].countryCode} style={{ marginRight: 8 }} />,
|
||||
label: languages[lang].name,
|
||||
}));
|
||||
|
||||
@@ -52,7 +50,6 @@ export const Header: React.FC<RefineThemedLayoutV2HeaderProps> = ({ sticky }) =>
|
||||
>
|
||||
<Button type="text">
|
||||
<Space>
|
||||
<span className={"fi fi-" + languages[currentLocale ?? "en"].countryCode} />
|
||||
{languages[currentLocale ?? "en"].name}
|
||||
<DownOutlined />
|
||||
</Space>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useTranslate } from "@refinedev/core";
|
||||
import { QrScanner } from "@yudiel/react-qr-scanner";
|
||||
import { FloatButton, Modal, Space } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useNavigate } from "react-router";
|
||||
|
||||
const QRCodeScannerModal: React.FC = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
height: 4em;
|
||||
}
|
||||
|
||||
.spool-icon.no-margin {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.spool-icon * {
|
||||
flex: 1 1 0px;
|
||||
border-radius: 2px;
|
||||
|
||||
@@ -3,13 +3,15 @@ import "./spoolIcon.css";
|
||||
interface Props {
|
||||
color: string | { colors: string[]; vertical: boolean };
|
||||
size?: "small" | "large";
|
||||
no_margin? : boolean
|
||||
}
|
||||
|
||||
export default function SpoolIcon(props: Props) {
|
||||
export default function SpoolIcon(props: Readonly<Props>) {
|
||||
let dirClass = "vertical";
|
||||
let cols = [];
|
||||
let size = props.size ? props.size : "small";
|
||||
|
||||
let no_margin = props.no_margin ? "no-margin" : "";
|
||||
|
||||
if (typeof props.color === "string") {
|
||||
cols = [props.color];
|
||||
} else {
|
||||
@@ -18,7 +20,7 @@ export default function SpoolIcon(props: Props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={"spool-icon " + dirClass + " " + size}>
|
||||
<div className={"spool-icon " + dirClass + " " + size + " " + no_margin}>
|
||||
{cols.map((col) => (
|
||||
<div
|
||||
key={col}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { getBasePath } from "./utils/url";
|
||||
|
||||
interface Language {
|
||||
name: string;
|
||||
countryCode: string;
|
||||
fullCode: string;
|
||||
djs: () => Promise<ILocale>;
|
||||
}
|
||||
@@ -16,131 +15,130 @@ interface Language {
|
||||
* List of languages to load
|
||||
* The key of each object is the folder name in the locales dir.
|
||||
* name: Name of the language in the list
|
||||
* countryCode: Country code of the country's flag to display for this language
|
||||
* fullCode: Full language code, used for Ant Design's locale
|
||||
* djs: Function to load the dayjs locale, see https://github.com/iamkun/dayjs/tree/dev/src/locale for list of locales
|
||||
*/
|
||||
export const languages: { [key: string]: Language } = {
|
||||
["en"]: {
|
||||
name: "English",
|
||||
countryCode: "gb",
|
||||
fullCode: "en-GB",
|
||||
djs: () => import("dayjs/locale/en"),
|
||||
},
|
||||
["sv"]: {
|
||||
name: "Svenska",
|
||||
countryCode: "se",
|
||||
fullCode: "sv-SE",
|
||||
djs: () => import("dayjs/locale/sv"),
|
||||
},
|
||||
["de"]: {
|
||||
name: "Deutsch",
|
||||
countryCode: "de",
|
||||
fullCode: "de-DE",
|
||||
djs: () => import("dayjs/locale/de"),
|
||||
},
|
||||
["es"]: {
|
||||
name: "Español",
|
||||
countryCode: "es",
|
||||
fullCode: "es-ES",
|
||||
djs: () => import("dayjs/locale/es"),
|
||||
},
|
||||
["zh"]: {
|
||||
name: "简体中文",
|
||||
countryCode: "cn",
|
||||
fullCode: "zh-CN",
|
||||
djs: () => import("dayjs/locale/zh-cn"),
|
||||
},
|
||||
["zh-Hant"]: {
|
||||
name: "繁體中文",
|
||||
countryCode: "cn",
|
||||
fullCode: "zh-TW",
|
||||
djs: () => import("dayjs/locale/zh-hk"),
|
||||
},
|
||||
["pl"]: {
|
||||
name: "Polski",
|
||||
countryCode: "pl",
|
||||
fullCode: "pl-PL",
|
||||
djs: () => import("dayjs/locale/pl"),
|
||||
},
|
||||
["ru"]: {
|
||||
name: "Русский",
|
||||
countryCode: "ru",
|
||||
fullCode: "ru-RU",
|
||||
djs: () => import("dayjs/locale/ru"),
|
||||
},
|
||||
["cs"]: {
|
||||
name: "Česky",
|
||||
countryCode: "cz",
|
||||
fullCode: "cs-CZ",
|
||||
djs: () => import("dayjs/locale/cs"),
|
||||
},
|
||||
["nb-NO"]: {
|
||||
name: "Norsk bokmål",
|
||||
countryCode: "no",
|
||||
fullCode: "nb-NO",
|
||||
djs: () => import("dayjs/locale/nb"),
|
||||
},
|
||||
["nl"]: {
|
||||
name: "Nederlands",
|
||||
countryCode: "nl",
|
||||
fullCode: "nl-NL",
|
||||
djs: () => import("dayjs/locale/nl"),
|
||||
},
|
||||
["fr"]: {
|
||||
name: "Français",
|
||||
countryCode: "fr",
|
||||
fullCode: "fr-FR",
|
||||
djs: () => import("dayjs/locale/fr"),
|
||||
},
|
||||
["hu"]: {
|
||||
name: "Magyar",
|
||||
countryCode: "hu",
|
||||
fullCode: "hu-HU",
|
||||
djs: () => import("dayjs/locale/hu"),
|
||||
},
|
||||
["it"]: {
|
||||
name: "Italiano",
|
||||
countryCode: "it",
|
||||
fullCode: "it-IT",
|
||||
djs: () => import("dayjs/locale/it"),
|
||||
},
|
||||
["uk"]: {
|
||||
name: "Українська",
|
||||
countryCode: "ua",
|
||||
fullCode: "uk-UA",
|
||||
djs: () => import("dayjs/locale/uk"),
|
||||
},
|
||||
["el"]: {
|
||||
name: "Ελληνικά",
|
||||
countryCode: "gr",
|
||||
fullCode: "el-GR",
|
||||
djs: () => import("dayjs/locale/el"),
|
||||
},
|
||||
["da"]: {
|
||||
name: "Dansk",
|
||||
countryCode: "dk",
|
||||
fullCode: "da-DK",
|
||||
djs: () => import("dayjs/locale/da"),
|
||||
},
|
||||
["pt"]: {
|
||||
name: "Português",
|
||||
countryCode: "pt",
|
||||
fullCode: "pt-PT",
|
||||
djs: () => import("dayjs/locale/pt"),
|
||||
},
|
||||
["fa"]: {
|
||||
name: "فارسی",
|
||||
countryCode: "ir",
|
||||
fullCode: "fa-IR",
|
||||
djs: () => import("dayjs/locale/fa"),
|
||||
},
|
||||
["ro"]: {
|
||||
name: "Român",
|
||||
countryCode: "ro",
|
||||
fullCode: "ro-RO",
|
||||
djs: () => import("dayjs/locale/ro"),
|
||||
},
|
||||
["ja"]: {
|
||||
name: "日本語",
|
||||
fullCode: "ja-JP",
|
||||
djs: () => import("dayjs/locale/ja"),
|
||||
},
|
||||
["pt-BR"]: {
|
||||
name: "Português (Brasil)",
|
||||
fullCode: "pt-BR",
|
||||
djs: () => import("dayjs/locale/pt-br"),
|
||||
},
|
||||
["ta"]: {
|
||||
name: "தமிழ்",
|
||||
fullCode: "ta-IN",
|
||||
djs: () => import("dayjs/locale/ta"),
|
||||
},
|
||||
["th"]: {
|
||||
name: "ไทย",
|
||||
fullCode: "th-TH",
|
||||
djs: () => import("dayjs/locale/th"),
|
||||
},
|
||||
};
|
||||
|
||||
i18n
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import '@ant-design/v5-patch-for-react-19';
|
||||
import React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useEffect, useState } from "react";
|
||||
import { ExtraFieldFormItem, ParsedExtras, StringifiedExtras } from "../../components/extraFields";
|
||||
import { FilamentImportModal } from "../../components/filamentImportModal";
|
||||
import { MultiColorPicker } from "../../components/multiColorPicker";
|
||||
import { numberFormatter, numberParser, numberParserAllowEmpty } from "../../utils/parsing";
|
||||
import { formatNumberOnUserInput, numberParser, numberParserAllowEmpty } from "../../utils/parsing";
|
||||
import { ExternalFilament } from "../../utils/queryExternalDB";
|
||||
import { EntityType, useGetFields } from "../../utils/queryFields";
|
||||
import { getCurrencySymbol, useCurrency } from "../../utils/settings";
|
||||
@@ -250,7 +250,7 @@ export const FilamentCreate: React.FC<IResourceComponentsProps & CreateOrClonePr
|
||||
<InputNumber
|
||||
addonAfter={getCurrencySymbol(undefined, currency)}
|
||||
precision={2}
|
||||
formatter={numberFormatter}
|
||||
formatter={formatNumberOnUserInput}
|
||||
parser={numberParserAllowEmpty}
|
||||
/>
|
||||
</Form.Item>
|
||||
@@ -266,7 +266,7 @@ export const FilamentCreate: React.FC<IResourceComponentsProps & CreateOrClonePr
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber addonAfter="g/cm³" precision={2} formatter={numberFormatter} parser={numberParser} />
|
||||
<InputNumber addonAfter="g/cm³" precision={2} formatter={formatNumberOnUserInput} parser={numberParser} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("filament.fields.diameter")}
|
||||
@@ -280,7 +280,7 @@ export const FilamentCreate: React.FC<IResourceComponentsProps & CreateOrClonePr
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber addonAfter="mm" precision={2} formatter={numberFormatter} parser={numberParser} />
|
||||
<InputNumber addonAfter="mm" precision={2} formatter={formatNumberOnUserInput} parser={numberParser} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("filament.fields.weight")}
|
||||
|
||||
@@ -6,7 +6,7 @@ import dayjs from "dayjs";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ExtraFieldFormItem, ParsedExtras, StringifiedExtras } from "../../components/extraFields";
|
||||
import { MultiColorPicker } from "../../components/multiColorPicker";
|
||||
import { numberFormatter, numberParser, numberParserAllowEmpty } from "../../utils/parsing";
|
||||
import { formatNumberOnUserInput, numberParser, numberParserAllowEmpty } from "../../utils/parsing";
|
||||
import { EntityType, useGetFields } from "../../utils/queryFields";
|
||||
import { getCurrencySymbol, useCurrency } from "../../utils/settings";
|
||||
import { IVendor } from "../vendors/model";
|
||||
@@ -63,6 +63,9 @@ export const FilamentEdit: React.FC<IResourceComponentsProps> = () => {
|
||||
const originalOnFinish = formProps.onFinish;
|
||||
formProps.onFinish = (allValues: IFilamentParsedExtras) => {
|
||||
if (allValues !== undefined && allValues !== null) {
|
||||
if (colorType == "single") {
|
||||
allValues.multi_color_hexes = "";
|
||||
}
|
||||
// Lot of stupidity here to make types work
|
||||
const stringifiedAllValues = StringifiedExtras<IFilamentParsedExtras>(allValues);
|
||||
originalOnFinish?.({
|
||||
@@ -166,7 +169,7 @@ export const FilamentEdit: React.FC<IResourceComponentsProps> = () => {
|
||||
return e?.toHex();
|
||||
}}
|
||||
>
|
||||
<ColorPicker format="hex" />
|
||||
<ColorPicker />
|
||||
</Form.Item>
|
||||
)}
|
||||
{colorType == "multi" && (
|
||||
@@ -225,7 +228,7 @@ export const FilamentEdit: React.FC<IResourceComponentsProps> = () => {
|
||||
<InputNumber
|
||||
addonAfter={getCurrencySymbol(undefined, currency)}
|
||||
precision={2}
|
||||
formatter={numberFormatter}
|
||||
formatter={formatNumberOnUserInput}
|
||||
parser={numberParserAllowEmpty}
|
||||
/>
|
||||
</Form.Item>
|
||||
@@ -241,7 +244,7 @@ export const FilamentEdit: React.FC<IResourceComponentsProps> = () => {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber addonAfter="g/cm³" precision={2} formatter={numberFormatter} parser={numberParser} />
|
||||
<InputNumber addonAfter="g/cm³" precision={2} formatter={formatNumberOnUserInput} parser={numberParser} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("filament.fields.diameter")}
|
||||
@@ -255,7 +258,7 @@ export const FilamentEdit: React.FC<IResourceComponentsProps> = () => {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber addonAfter="mm" precision={2} formatter={numberFormatter} parser={numberParser} />
|
||||
<InputNumber addonAfter="mm" precision={2} formatter={formatNumberOnUserInput} parser={numberParser} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("filament.fields.weight")}
|
||||
|
||||
@@ -5,28 +5,28 @@ import { Button, Dropdown, Table } from "antd";
|
||||
import dayjs from "dayjs";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useNavigate } from "react-router";
|
||||
import {
|
||||
ActionsColumn,
|
||||
CustomFieldColumn,
|
||||
DateColumn,
|
||||
FilteredQueryColumn,
|
||||
NumberColumn,
|
||||
RichColumn,
|
||||
SortedColumn,
|
||||
SpoolIconColumn,
|
||||
ActionsColumn,
|
||||
CustomFieldColumn,
|
||||
DateColumn,
|
||||
FilteredQueryColumn,
|
||||
NumberColumn,
|
||||
RichColumn,
|
||||
SortedColumn,
|
||||
SpoolIconColumn,
|
||||
} from "../../components/column";
|
||||
import { useLiveify } from "../../components/liveify";
|
||||
import {
|
||||
useSpoolmanArticleNumbers,
|
||||
useSpoolmanFilamentNames,
|
||||
useSpoolmanMaterials,
|
||||
useSpoolmanVendors,
|
||||
useSpoolmanArticleNumbers,
|
||||
useSpoolmanFilamentNames,
|
||||
useSpoolmanMaterials,
|
||||
useSpoolmanVendors,
|
||||
} from "../../components/otherModels";
|
||||
import { removeUndefined } from "../../utils/filtering";
|
||||
import { EntityType, useGetFields } from "../../utils/queryFields";
|
||||
import { TableState, useInitialTableState, useStoreInitialState } from "../../utils/saveload";
|
||||
import { useCurrency } from "../../utils/settings";
|
||||
import { useCurrencyFormatter } from "../../utils/settings";
|
||||
import { IFilament } from "./model";
|
||||
|
||||
dayjs.extend(utc);
|
||||
@@ -77,7 +77,7 @@ export const FilamentList: React.FC<IResourceComponentsProps> = () => {
|
||||
const invalidate = useInvalidate();
|
||||
const navigate = useNavigate();
|
||||
const extraFields = useGetFields(EntityType.filament);
|
||||
const currency = useCurrency();
|
||||
const currencyFormatter = useCurrencyFormatter();
|
||||
|
||||
const allColumnsWithExtraFields = [...allColumns, ...(extraFields.data?.map((field) => "extra." + field.key) ?? [])];
|
||||
|
||||
@@ -263,12 +263,10 @@ export const FilamentList: React.FC<IResourceComponentsProps> = () => {
|
||||
align: "right",
|
||||
width: 80,
|
||||
render: (_, obj: IFilamentCollapsed) => {
|
||||
return obj.price?.toLocaleString(undefined, {
|
||||
style: "currency",
|
||||
currencyDisplay: "narrowSymbol",
|
||||
currency: currency,
|
||||
notation: "compact",
|
||||
});
|
||||
if (obj.price === undefined) {
|
||||
return "";
|
||||
}
|
||||
return currencyFormatter.format(obj.price);
|
||||
},
|
||||
}),
|
||||
NumberColumn({
|
||||
|
||||
@@ -4,13 +4,13 @@ import { Button, Typography } from "antd";
|
||||
import dayjs from "dayjs";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useNavigate } from "react-router";
|
||||
import { ExtraFieldDisplay } from "../../components/extraFields";
|
||||
import { NumberFieldUnit } from "../../components/numberField";
|
||||
import SpoolIcon from "../../components/spoolIcon";
|
||||
import { enrichText } from "../../utils/parsing";
|
||||
import { EntityType, useGetFields } from "../../utils/queryFields";
|
||||
import { useCurrency } from "../../utils/settings";
|
||||
import { useCurrencyFormatter } from "../../utils/settings";
|
||||
import { IFilament } from "./model";
|
||||
dayjs.extend(utc);
|
||||
|
||||
@@ -20,7 +20,7 @@ export const FilamentShow: React.FC<IResourceComponentsProps> = () => {
|
||||
const t = useTranslate();
|
||||
const navigate = useNavigate();
|
||||
const extraFields = useGetFields(EntityType.filament);
|
||||
const currency = useCurrency();
|
||||
const currencyFormatter = useCurrencyFormatter();
|
||||
const { queryResult } = useShow<IFilament>({
|
||||
liveMode: "auto",
|
||||
});
|
||||
@@ -88,19 +88,12 @@ export const FilamentShow: React.FC<IResourceComponentsProps> = () => {
|
||||
<Title level={5}>{t("filament.fields.name")}</Title>
|
||||
<TextField value={record?.name} />
|
||||
<Title level={5}>{t("filament.fields.color_hex")}</Title>
|
||||
{colorObj && <SpoolIcon color={colorObj} size="large" />}
|
||||
{colorObj && <SpoolIcon color={colorObj} size="large" no_margin />}
|
||||
{record?.color_hex && <TextField value={`#${record?.color_hex}`} />}
|
||||
<Title level={5}>{t("filament.fields.material")}</Title>
|
||||
<TextField value={record?.material} />
|
||||
<Title level={5}>{t("filament.fields.price")}</Title>
|
||||
<NumberField
|
||||
value={record?.price ?? ""}
|
||||
options={{
|
||||
style: "currency",
|
||||
currency: currency,
|
||||
currencyDisplay: "narrowSymbol",
|
||||
notation: "compact",
|
||||
}}
|
||||
/>
|
||||
<TextField value={record?.price ? currencyFormatter.format(record.price) : ""} />
|
||||
<Title level={5}>{t("filament.fields.density")}</Title>
|
||||
<NumberFieldUnit
|
||||
value={record?.density ?? ""}
|
||||
|
||||
@@ -7,7 +7,7 @@ import dayjs from "dayjs";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import React from "react";
|
||||
import { Trans } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Link } from "react-router";
|
||||
|
||||
dayjs.extend(utc);
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import Title from "antd/es/typography/Title";
|
||||
import dayjs from "dayjs";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import React, { ReactNode } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Link } from "react-router";
|
||||
import Logo from "../../icon.svg?react";
|
||||
import { ISpool } from "../spools/model";
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import dayjs from "dayjs";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Link } from "react-router";
|
||||
import SpoolIcon from "../../../components/spoolIcon";
|
||||
import { formatWeight } from "../../../utils/parsing";
|
||||
import { ISpool } from "../../spools/model";
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Content } from "antd/es/layout/layout";
|
||||
import dayjs from "dayjs";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import React from "react";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { useNavigate, useSearchParams } from "react-router";
|
||||
import SpoolQRCodePrintingDialog from "./spoolQrCodePrintingDialog";
|
||||
import SpoolSelectModal from "./spoolSelectModal";
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ReactElement } from "react";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { useGetSetting, useSetSetting } from "../../utils/querySettings";
|
||||
import { ISpool } from "../spools/model";
|
||||
@@ -75,7 +76,7 @@ function getTagValue(tag: string, obj: GenericObject): any {
|
||||
return value;
|
||||
}
|
||||
|
||||
function applyNewline(text: string): JSX.Element[] {
|
||||
function applyNewline(text: string): ReactElement[] {
|
||||
return text.split("\n").map((line, idx, arr) => (
|
||||
<span key={idx}>
|
||||
{line}
|
||||
@@ -84,7 +85,7 @@ function applyNewline(text: string): JSX.Element[] {
|
||||
));
|
||||
}
|
||||
|
||||
function applyTextFormatting(text: string): JSX.Element[] {
|
||||
function applyTextFormatting(text: string): ReactElement[] {
|
||||
const regex = /\*\*([\w\W]*?)\*\*/g;
|
||||
const parts = text.split(regex);
|
||||
// Map over the parts and wrap matched text with <b> tags
|
||||
@@ -96,7 +97,7 @@ function applyTextFormatting(text: string): JSX.Element[] {
|
||||
return elements;
|
||||
}
|
||||
|
||||
export function renderLabelContents(template: string, spool: ISpool): JSX.Element {
|
||||
export function renderLabelContents(template: string, spool: ISpool): ReactElement {
|
||||
// Find all {tags} in the template string and loop over them
|
||||
// let matches = [...template.matchAll(/(?:{(.*?))?{(.*?)}(.*?)(?:}(.*?))?/gs)];
|
||||
let matches = [...template.matchAll(/{(?:[^}{]|{[^}{]*})*}/gs)];
|
||||
|
||||
@@ -15,19 +15,19 @@ import {
|
||||
Space,
|
||||
} from "antd";
|
||||
import * as htmlToImage from "html-to-image";
|
||||
import React, { useRef } from "react";
|
||||
import ReactToPrint from "react-to-print";
|
||||
import React, { ReactElement, useRef } from "react";
|
||||
import { useReactToPrint } from "react-to-print";
|
||||
import { useSavedState } from "../../utils/saveload";
|
||||
import { PrintSettings } from "./printing";
|
||||
|
||||
interface PrintingDialogProps {
|
||||
items: JSX.Element[];
|
||||
items: ReactElement[];
|
||||
printSettings: PrintSettings;
|
||||
setPrintSettings: (setPrintSettings: PrintSettings) => void;
|
||||
style?: string;
|
||||
extraSettings?: JSX.Element;
|
||||
extraSettingsStart?: JSX.Element;
|
||||
extraButtons?: JSX.Element;
|
||||
extraSettings?: ReactElement;
|
||||
extraSettingsStart?: ReactElement;
|
||||
extraButtons?: ReactElement;
|
||||
}
|
||||
|
||||
interface PaperDimensions {
|
||||
@@ -90,7 +90,8 @@ const PrintingDialog: React.FC<PrintingDialogProps> = ({
|
||||
const paperWidth = paperSize === "custom" ? customPaperSize.width : paperDimensions[paperSize].width;
|
||||
const paperHeight = paperSize === "custom" ? customPaperSize.height : paperDimensions[paperSize].height;
|
||||
|
||||
const printRef = useRef<HTMLDivElement>(null);
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const reactToPrintFn = useReactToPrint({ contentRef });
|
||||
|
||||
const itemWidth = (paperWidth - margin.left - margin.right - spacing.horizontal) / paperColumns - spacing.horizontal;
|
||||
const itemHeight = (paperHeight - margin.top - margin.bottom - spacing.vertical) / paperRows - spacing.vertical;
|
||||
@@ -223,7 +224,7 @@ const PrintingDialog: React.FC<PrintingDialogProps> = ({
|
||||
>
|
||||
<div
|
||||
className="print-container"
|
||||
ref={printRef}
|
||||
ref={contentRef}
|
||||
style={{
|
||||
transform: `scale(${previewScale})`,
|
||||
transformOrigin: "top left",
|
||||
@@ -817,15 +818,9 @@ const PrintingDialog: React.FC<PrintingDialogProps> = ({
|
||||
<Button type="primary" icon={<FileImageOutlined />} size="large" onClick={saveAsImage}>
|
||||
{t("printing.generic.saveAsImage")}
|
||||
</Button>
|
||||
<ReactToPrint
|
||||
key="print-button"
|
||||
trigger={() => (
|
||||
<Button type="primary" icon={<PrinterOutlined />} size="large">
|
||||
{t("printing.generic.print")}
|
||||
</Button>
|
||||
)}
|
||||
content={() => printRef.current}
|
||||
/>
|
||||
<Button type="primary" icon={<PrinterOutlined />} size="large" onClick={() => reactToPrintFn()}>
|
||||
{t("printing.generic.print")}
|
||||
</Button>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useTranslate } from "@refinedev/core";
|
||||
import { Col, Form, InputNumber, QRCode, Radio, RadioChangeEvent, Row, Slider, Switch, Typography } from "antd";
|
||||
import { ReactElement } from "react";
|
||||
import { getBasePath } from "../../utils/url";
|
||||
import { QRCodePrintSettings } from "./printing";
|
||||
import PrintingDialog from "./printingDialog";
|
||||
|
||||
@@ -7,7 +9,7 @@ const { Text } = Typography;
|
||||
|
||||
interface QRCodeData {
|
||||
value: string;
|
||||
label?: JSX.Element;
|
||||
label?: ReactElement;
|
||||
errorLevel?: "L" | "M" | "Q" | "H";
|
||||
}
|
||||
|
||||
@@ -15,9 +17,9 @@ interface QRCodePrintingDialogProps {
|
||||
items: QRCodeData[];
|
||||
printSettings: QRCodePrintSettings;
|
||||
setPrintSettings: (setPrintSettings: QRCodePrintSettings) => void;
|
||||
extraSettings?: JSX.Element;
|
||||
extraSettingsStart?: JSX.Element;
|
||||
extraButtons?: JSX.Element;
|
||||
extraSettings?: ReactElement;
|
||||
extraSettingsStart?: ReactElement;
|
||||
extraButtons?: ReactElement;
|
||||
baseUrlRoot: string;
|
||||
useHTTPUrl: boolean;
|
||||
setUseHTTPUrl: (value: boolean) => void;
|
||||
@@ -47,7 +49,7 @@ const QRCodePrintingDialog: React.FC<QRCodePrintingDialogProps> = ({
|
||||
<div className="print-qrcode-container">
|
||||
<QRCode
|
||||
className="print-qrcode"
|
||||
icon={showQRCodeMode === "withIcon" ? "/favicon.svg" : undefined}
|
||||
icon={showQRCodeMode === "withIcon" ? getBasePath() + "/favicon.svg" : undefined}
|
||||
value={item.value}
|
||||
errorLevel={item.errorLevel}
|
||||
type="svg"
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useTable } from "@refinedev/antd";
|
||||
import { Button, Checkbox, Col, message, Row, Space, Table } from "antd";
|
||||
import { t } from "i18next";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useNavigate } from "react-router";
|
||||
import { FilteredQueryColumn, SortedColumn, SpoolIconColumn } from "../../components/column";
|
||||
import { useSpoolmanFilamentFilter, useSpoolmanMaterials } from "../../components/otherModels";
|
||||
import { removeUndefined } from "../../utils/filtering";
|
||||
|
||||
@@ -22,7 +22,7 @@ import timezone from "dayjs/plugin/timezone";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { useState } from "react";
|
||||
import { Trans } from "react-i18next";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useParams } from "react-router";
|
||||
import { DateTimePicker } from "../../components/dateTimePicker";
|
||||
import { InputNumberRange } from "../../components/inputNumberRange";
|
||||
import { EntityType, Field, FieldType, useDeleteField, useGetFields, useSetField } from "../../utils/queryFields";
|
||||
@@ -488,10 +488,16 @@ export function ExtraFieldsSettings() {
|
||||
} else if (Array.isArray(val) && record.field.field_type === FieldType.choice) {
|
||||
return val.join(", ");
|
||||
} else if (
|
||||
(Array.isArray(val) && record.field.field_type === FieldType.integer_range) ||
|
||||
record.field.field_type === FieldType.float_range
|
||||
Array.isArray(val) && (
|
||||
record.field.field_type === FieldType.integer_range || record.field.field_type === FieldType.float_range
|
||||
)
|
||||
) {
|
||||
return `${val[0]} \u2013 ${val[1]}`;
|
||||
let lower = val[0] ?? "";
|
||||
let upper = val[1] ?? "";
|
||||
if (lower === "" && upper === "") {
|
||||
return null;
|
||||
}
|
||||
return `${lower} \u2013 ${upper}`;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 { useGetSettings, useSetSetting } from "../../utils/querySettings";
|
||||
|
||||
@@ -7,6 +7,7 @@ export function GeneralSettings() {
|
||||
const settings = useGetSettings();
|
||||
const setBaseUrl = useSetSetting("base_url");
|
||||
const setCurrency = useSetSetting("currency");
|
||||
const setRoundPrices = useSetSetting("round_prices");
|
||||
const [form] = Form.useForm();
|
||||
const [messageApi, contextHolder] = message.useMessage();
|
||||
const t = useTranslate();
|
||||
@@ -17,6 +18,7 @@ export function GeneralSettings() {
|
||||
form.setFieldsValue({
|
||||
currency: JSON.parse(settings.data.currency.value),
|
||||
base_url: JSON.parse(settings.data.base_url.value),
|
||||
round_prices: JSON.parse(settings.data.round_prices.value),
|
||||
});
|
||||
}
|
||||
}, [settings.data, form]);
|
||||
@@ -29,7 +31,7 @@ export function GeneralSettings() {
|
||||
}, [setCurrency.isSuccess, messageApi, t]);
|
||||
|
||||
// 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
|
||||
if (settings.data?.currency.value !== JSON.stringify(values.currency)) {
|
||||
setCurrency.mutate(values.currency);
|
||||
@@ -38,61 +40,74 @@ export function GeneralSettings() {
|
||||
if (settings.data?.base_url.value !== JSON.stringify(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 (
|
||||
<>
|
||||
<Form
|
||||
form={form}
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 16 }}
|
||||
initialValues={{
|
||||
currency: settings.data?.currency.value,
|
||||
}}
|
||||
onFinish={onFinish}
|
||||
style={{
|
||||
maxWidth: "600px",
|
||||
margin: "0 auto",
|
||||
}}
|
||||
return (<>
|
||||
<Form
|
||||
form={form}
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 16 }}
|
||||
initialValues={{
|
||||
currency: settings.data?.currency.value,
|
||||
round_prices: settings.data?.round_prices.value,
|
||||
}}
|
||||
onFinish={onFinish}
|
||||
style={{
|
||||
maxWidth: "600px",
|
||||
margin: "0 auto",
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
label={t("settings.general.currency.label")}
|
||||
name="currency"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
pattern: /^[A-Z]{3}$/,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Form.Item
|
||||
label={t("settings.general.currency.label")}
|
||||
name="currency"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
pattern: /^[A-Z]{3}$/,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t("settings.general.base_url.label")}
|
||||
tooltip={t("settings.general.base_url.tooltip")}
|
||||
name="base_url"
|
||||
rules={[
|
||||
{
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
pattern: /^https?:\/\/.+(?<!\/)$/,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input placeholder="https://example.com:8000" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("settings.general.base_url.label")}
|
||||
tooltip={t("settings.general.base_url.tooltip")}
|
||||
name="base_url"
|
||||
rules={[
|
||||
{
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
pattern: /^https?:\/\/.+(?<!\/)$/,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input placeholder="https://example.com:8000" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
|
||||
<Button type="primary" htmlType="submit" loading={settings.isFetching || setCurrency.isLoading}>
|
||||
{t("buttons.save")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
{contextHolder}
|
||||
</>
|
||||
);
|
||||
<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 }}>
|
||||
<Button type="primary" htmlType="submit" loading={settings.isFetching || setCurrency.isLoading}>
|
||||
{t("buttons.save")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
{contextHolder}
|
||||
</>);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Content } from "antd/es/layout/layout";
|
||||
import dayjs from "dayjs";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import React from "react";
|
||||
import { Route, Routes, useNavigate } from "react-router-dom";
|
||||
import { Route, Routes, useNavigate } from "react-router";
|
||||
import { ExtraFieldsSettings } from "./extraFieldsSettings";
|
||||
import { GeneralSettings } from "./generalSettings";
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ExtraFieldFormItem, ParsedExtras, StringifiedExtras } from "../../compo
|
||||
import { useSpoolmanLocations } from "../../components/otherModels";
|
||||
import { searchMatches } from "../../utils/filtering";
|
||||
import "../../utils/overrides.css";
|
||||
import { numberFormatter, numberParser, numberParserAllowEmpty } from "../../utils/parsing";
|
||||
import { formatNumberOnUserInput, numberParser, numberParserAllowEmpty } from "../../utils/parsing";
|
||||
import { EntityType, useGetFields } from "../../utils/queryFields";
|
||||
import { getCurrencySymbol, useCurrency } from "../../utils/settings";
|
||||
import { createFilamentFromExternal } from "../filaments/functions";
|
||||
@@ -330,7 +330,7 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
|
||||
<InputNumber
|
||||
addonAfter={getCurrencySymbol(undefined, currency)}
|
||||
precision={2}
|
||||
formatter={numberFormatter}
|
||||
formatter={formatNumberOnUserInput}
|
||||
parser={numberParserAllowEmpty}
|
||||
/>
|
||||
</Form.Item>
|
||||
@@ -391,7 +391,7 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
|
||||
min={0}
|
||||
addonAfter="g"
|
||||
precision={1}
|
||||
formatter={numberFormatter}
|
||||
formatter={formatNumberOnUserInput}
|
||||
parser={numberParser}
|
||||
disabled={weightToEnter != WeightToEnter.used_weight}
|
||||
value={usedWeight}
|
||||
@@ -409,7 +409,7 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
|
||||
min={0}
|
||||
addonAfter="g"
|
||||
precision={1}
|
||||
formatter={numberFormatter}
|
||||
formatter={formatNumberOnUserInput}
|
||||
parser={numberParser}
|
||||
disabled={weightToEnter != WeightToEnter.remaining_weight}
|
||||
value={getRemainingWeight()}
|
||||
@@ -427,7 +427,7 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
|
||||
min={0}
|
||||
addonAfter="g"
|
||||
precision={1}
|
||||
formatter={numberFormatter}
|
||||
formatter={formatNumberOnUserInput}
|
||||
parser={numberParser}
|
||||
disabled={weightToEnter != WeightToEnter.measured_weight}
|
||||
value={getMeasuredWeight()}
|
||||
|
||||
@@ -5,11 +5,11 @@ import TextArea from "antd/es/input/TextArea";
|
||||
import { message } from "antd/lib";
|
||||
import dayjs from "dayjs";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { useNavigate, useSearchParams } from "react-router";
|
||||
import { ExtraFieldFormItem, ParsedExtras, StringifiedExtras } from "../../components/extraFields";
|
||||
import { useSpoolmanLocations } from "../../components/otherModels";
|
||||
import { searchMatches } from "../../utils/filtering";
|
||||
import { numberFormatter, numberParser, numberParserAllowEmpty } from "../../utils/parsing";
|
||||
import { formatNumberOnUserInput, numberParser, numberParserAllowEmpty } from "../../utils/parsing";
|
||||
import { EntityType, useGetFields } from "../../utils/queryFields";
|
||||
import { getCurrencySymbol, useCurrency } from "../../utils/settings";
|
||||
import { createFilamentFromExternal } from "../filaments/functions";
|
||||
@@ -320,7 +320,7 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
|
||||
<InputNumber
|
||||
addonAfter={getCurrencySymbol(undefined, currency)}
|
||||
precision={2}
|
||||
formatter={numberFormatter}
|
||||
formatter={formatNumberOnUserInput}
|
||||
parser={numberParserAllowEmpty}
|
||||
/>
|
||||
</Form.Item>
|
||||
@@ -380,7 +380,7 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
|
||||
min={0}
|
||||
addonAfter="g"
|
||||
precision={1}
|
||||
formatter={numberFormatter}
|
||||
formatter={formatNumberOnUserInput}
|
||||
parser={numberParser}
|
||||
disabled={weightToEnter != WeightToEnter.used_weight}
|
||||
value={usedWeight}
|
||||
@@ -394,7 +394,7 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
|
||||
min={0}
|
||||
addonAfter="g"
|
||||
precision={1}
|
||||
formatter={numberFormatter}
|
||||
formatter={formatNumberOnUserInput}
|
||||
parser={numberParser}
|
||||
disabled={weightToEnter != WeightToEnter.remaining_weight}
|
||||
value={getRemainingWeight()}
|
||||
@@ -408,7 +408,7 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
|
||||
min={0}
|
||||
addonAfter="g"
|
||||
precision={1}
|
||||
formatter={numberFormatter}
|
||||
formatter={formatNumberOnUserInput}
|
||||
parser={numberParser}
|
||||
disabled={weightToEnter != WeightToEnter.measured_weight}
|
||||
value={getMeasuredWeight()}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useSelect, useTranslate } from "@refinedev/core";
|
||||
import { useQueries } from "@tanstack/react-query";
|
||||
import { Form, InputNumber, Modal, Radio } from "antd";
|
||||
import { useForm } from "antd/es/form/Form";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { useCallback, useMemo, useRef, useState } from "react";
|
||||
import { formatLength, formatWeight } from "../../utils/parsing";
|
||||
import { SpoolType, useGetExternalDBFilaments } from "../../utils/queryExternalDB";
|
||||
import { getAPIURL } from "../../utils/url";
|
||||
@@ -181,9 +181,13 @@ export function useSpoolAdjustModal() {
|
||||
|
||||
const [curSpool, setCurSpool] = useState<ISpool | null>(null);
|
||||
const [measurementType, setMeasurementType] = useState<MeasurementType>("length");
|
||||
const inputNumberRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
const openSpoolAdjustModal = useCallback((spool: ISpool) => {
|
||||
setCurSpool(spool);
|
||||
setTimeout(() => {
|
||||
inputNumberRef.current?.focus();
|
||||
}, 0);
|
||||
}, []);
|
||||
|
||||
const spoolAdjustModal = useMemo(() => {
|
||||
@@ -224,7 +228,7 @@ export function useSpoolAdjustModal() {
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("spool.form.adjust_filament_value")} name="filament_value">
|
||||
<InputNumber precision={1} addonAfter={measurementType === "length" ? "mm" : "g"} />
|
||||
<InputNumber ref={inputNumberRef} precision={1} addonAfter={measurementType === "length" ? "mm" : "g"} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import {
|
||||
EditOutlined,
|
||||
EyeOutlined,
|
||||
FilterOutlined,
|
||||
InboxOutlined,
|
||||
PlusSquareOutlined,
|
||||
PrinterOutlined,
|
||||
ToolOutlined,
|
||||
ToTopOutlined,
|
||||
EditOutlined,
|
||||
EyeOutlined,
|
||||
FilterOutlined,
|
||||
InboxOutlined,
|
||||
PlusSquareOutlined,
|
||||
PrinterOutlined,
|
||||
ToolOutlined,
|
||||
ToTopOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { List, useTable } from "@refinedev/antd";
|
||||
import { IResourceComponentsProps, useInvalidate, useNavigation, useTranslate } from "@refinedev/core";
|
||||
@@ -14,29 +14,29 @@ import { Button, Dropdown, Modal, Table } from "antd";
|
||||
import dayjs from "dayjs";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useNavigate } from "react-router";
|
||||
import {
|
||||
Action,
|
||||
ActionsColumn,
|
||||
CustomFieldColumn,
|
||||
DateColumn,
|
||||
FilteredQueryColumn,
|
||||
NumberColumn,
|
||||
RichColumn,
|
||||
SortedColumn,
|
||||
SpoolIconColumn,
|
||||
Action,
|
||||
ActionsColumn,
|
||||
CustomFieldColumn,
|
||||
DateColumn,
|
||||
FilteredQueryColumn,
|
||||
NumberColumn,
|
||||
RichColumn,
|
||||
SortedColumn,
|
||||
SpoolIconColumn,
|
||||
} from "../../components/column";
|
||||
import { useLiveify } from "../../components/liveify";
|
||||
import {
|
||||
useSpoolmanFilamentFilter,
|
||||
useSpoolmanLocations,
|
||||
useSpoolmanLotNumbers,
|
||||
useSpoolmanMaterials,
|
||||
useSpoolmanFilamentFilter,
|
||||
useSpoolmanLocations,
|
||||
useSpoolmanLotNumbers,
|
||||
useSpoolmanMaterials,
|
||||
} from "../../components/otherModels";
|
||||
import { removeUndefined } from "../../utils/filtering";
|
||||
import { EntityType, useGetFields } from "../../utils/queryFields";
|
||||
import { TableState, useInitialTableState, useSavedState, useStoreInitialState } from "../../utils/saveload";
|
||||
import { useCurrency } from "../../utils/settings";
|
||||
import { useCurrencyFormatter } from "../../utils/settings";
|
||||
import { setSpoolArchived, useSpoolAdjustModal } from "./functions";
|
||||
import { ISpool } from "./model";
|
||||
|
||||
@@ -102,7 +102,7 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
|
||||
const invalidate = useInvalidate();
|
||||
const navigate = useNavigate();
|
||||
const extraFields = useGetFields(EntityType.spool);
|
||||
const currency = useCurrency();
|
||||
const currencyFormatter = useCurrencyFormatter();
|
||||
const { openSpoolAdjustModal, spoolAdjustModal } = useSpoolAdjustModal();
|
||||
|
||||
const allColumnsWithExtraFields = [...allColumns, ...(extraFields.data?.map((field) => "extra." + field.key) ?? [])];
|
||||
@@ -381,12 +381,10 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
|
||||
align: "right",
|
||||
width: 80,
|
||||
render: (_, obj: ISpoolCollapsed) => {
|
||||
return obj.price?.toLocaleString(undefined, {
|
||||
style: "currency",
|
||||
currencyDisplay: "narrowSymbol",
|
||||
currency: currency,
|
||||
notation: "compact",
|
||||
});
|
||||
if (obj.price === undefined) {
|
||||
return "";
|
||||
}
|
||||
return currencyFormatter.format(obj.price);
|
||||
},
|
||||
}),
|
||||
NumberColumn({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getBasePath } from "../../utils/url";
|
||||
import { InboxOutlined, PrinterOutlined, ToTopOutlined, ToolOutlined } from "@ant-design/icons";
|
||||
import { DateField, NumberField, Show, TextField } from "@refinedev/antd";
|
||||
import { IResourceComponentsProps, useInvalidate, useShow, useTranslate } from "@refinedev/core";
|
||||
@@ -10,7 +11,7 @@ import { NumberFieldUnit } from "../../components/numberField";
|
||||
import SpoolIcon from "../../components/spoolIcon";
|
||||
import { enrichText } from "../../utils/parsing";
|
||||
import { EntityType, useGetFields } from "../../utils/queryFields";
|
||||
import { useCurrency } from "../../utils/settings";
|
||||
import { useCurrencyFormatter } from "../../utils/settings";
|
||||
import { IFilament } from "../filaments/model";
|
||||
import { setSpoolArchived, useSpoolAdjustModal } from "./functions";
|
||||
import { ISpool } from "./model";
|
||||
@@ -23,7 +24,7 @@ const { confirm } = Modal;
|
||||
export const SpoolShow: React.FC<IResourceComponentsProps> = () => {
|
||||
const t = useTranslate();
|
||||
const extraFields = useGetFields(EntityType.spool);
|
||||
const currency = useCurrency();
|
||||
const currencyFormatter = useCurrencyFormatter();
|
||||
const invalidate = useInvalidate();
|
||||
|
||||
const { queryResult } = useShow<ISpool>({
|
||||
@@ -33,13 +34,12 @@ export const SpoolShow: React.FC<IResourceComponentsProps> = () => {
|
||||
|
||||
const record = data?.data;
|
||||
|
||||
const spoolPrice = (item: ISpool) => {
|
||||
let spoolPrice = "";
|
||||
if (item.price === undefined) {
|
||||
spoolPrice = `${item.filament.price}`;
|
||||
return spoolPrice;
|
||||
const spoolPrice = (item?: ISpool) => {
|
||||
const price = item?.price ?? item?.filament.price;
|
||||
if (price === undefined) {
|
||||
return "";
|
||||
}
|
||||
return item.price;
|
||||
return currencyFormatter.format(price);
|
||||
};
|
||||
|
||||
// Provides the function to open the spool adjustment modal and the modal component itself
|
||||
@@ -128,7 +128,7 @@ export const SpoolShow: React.FC<IResourceComponentsProps> = () => {
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PrinterOutlined />}
|
||||
href={"/spool/print?spools=" + record?.id + "&return=" + encodeURIComponent(window.location.pathname)}
|
||||
href={getBasePath() + "/spool/print?spools=" + record?.id + "&return=" + encodeURIComponent(window.location.pathname)}
|
||||
>
|
||||
{t("printing.qrcode.button")}
|
||||
</Button>
|
||||
@@ -150,17 +150,10 @@ export const SpoolShow: React.FC<IResourceComponentsProps> = () => {
|
||||
<Title level={5}>{t("spool.fields.id")}</Title>
|
||||
<NumberField value={record?.id ?? ""} />
|
||||
<Title level={5}>{t("spool.fields.filament")}</Title>
|
||||
{colorObj && <SpoolIcon color={colorObj} />} <TextField value={record ? filamentURL(record?.filament) : ""} />
|
||||
{colorObj && <SpoolIcon color={colorObj} size="large" no_margin />}
|
||||
<TextField value={record ? filamentURL(record?.filament) : ""} />
|
||||
<Title level={5}>{t("spool.fields.price")}</Title>
|
||||
<NumberField
|
||||
value={record ? spoolPrice(record) : ""}
|
||||
options={{
|
||||
style: "currency",
|
||||
currency: currency,
|
||||
currencyDisplay: "narrowSymbol",
|
||||
notation: "compact",
|
||||
}}
|
||||
/>
|
||||
<TextField value={spoolPrice(record)} />
|
||||
<Title level={5}>{t("spool.fields.registered")}</Title>
|
||||
<DateField
|
||||
value={dayjs.utc(record?.registered).local()}
|
||||
|
||||
14
client/src/pages/vendors/list.tsx
vendored
14
client/src/pages/vendors/list.tsx
vendored
@@ -5,14 +5,14 @@ import { Button, Dropdown, Table } from "antd";
|
||||
import dayjs from "dayjs";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useNavigate } from "react-router";
|
||||
import {
|
||||
ActionsColumn,
|
||||
CustomFieldColumn,
|
||||
DateColumn,
|
||||
NumberColumn,
|
||||
RichColumn,
|
||||
SortedColumn,
|
||||
ActionsColumn,
|
||||
CustomFieldColumn,
|
||||
DateColumn,
|
||||
NumberColumn,
|
||||
RichColumn,
|
||||
SortedColumn,
|
||||
} from "../../components/column";
|
||||
import { useLiveify } from "../../components/liveify";
|
||||
import { removeUndefined } from "../../utils/filtering";
|
||||
|
||||
@@ -24,6 +24,22 @@ export function formatNumberWithSpaceSeparator(input: string): string {
|
||||
return formattedNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number formatter compatible with Ant Design's InputNumber component, handling UX properly.
|
||||
* @param value
|
||||
* @param info
|
||||
* @returns
|
||||
*/
|
||||
export function formatNumberOnUserInput(
|
||||
value: number | string | undefined,
|
||||
info: { userTyping: boolean; input: string }
|
||||
): string {
|
||||
if (info.userTyping) {
|
||||
return info.input;
|
||||
}
|
||||
return numberFormatter(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Number formatter that nicely formats numbers with correct decimal separator based on locale
|
||||
* Always uses blank space as thousands separator to prevent confusion with the decimal separator
|
||||
@@ -31,7 +47,6 @@ export function formatNumberWithSpaceSeparator(input: string): string {
|
||||
* @returns
|
||||
*/
|
||||
export function numberFormatter(value: number | string | undefined): string {
|
||||
console.log("numberformatter input: ", value);
|
||||
const formattedValue = value
|
||||
? Number(value).toLocaleString(undefined, {
|
||||
useGrouping: false, // Disable thousands separator and do it manually instead so it's always spaces
|
||||
@@ -117,10 +132,10 @@ export function enrichText(text: string | undefined) {
|
||||
*/
|
||||
export function formatWeight(weightInGrams: number, precision: number = 2): string {
|
||||
if (weightInGrams >= 1000) {
|
||||
const kilograms = (weightInGrams / 1000).toFixed(precision).replace(/\.?0+$/, ""); // Remove trailing zeros
|
||||
const kilograms = removeTrailingZeros((weightInGrams / 1000).toFixed(precision));
|
||||
return `${kilograms} kg`;
|
||||
} else {
|
||||
const grams = weightInGrams.toFixed(precision).replace(/\.?0+$/, ""); // Remove trailing zeros
|
||||
const grams = removeTrailingZeros(weightInGrams.toFixed(precision));
|
||||
return `${grams} g`;
|
||||
}
|
||||
}
|
||||
@@ -134,9 +149,30 @@ export function formatWeight(weightInGrams: number, precision: number = 2): stri
|
||||
*/
|
||||
export function formatLength(lengthInMillimeter: number, precision: number = 2): string {
|
||||
if (lengthInMillimeter >= 1000) {
|
||||
const meters = (lengthInMillimeter / 1000).toFixed(precision).replace(/\.?0+$/, ""); // Remove trailing zeros
|
||||
const meters = removeTrailingZeros((lengthInMillimeter / 1000).toFixed(precision));
|
||||
return `${meters} m`;
|
||||
} else {
|
||||
return `${lengthInMillimeter} mm`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes trailing zeros from a numeric string, including unnecessary decimal points.
|
||||
*
|
||||
* This function takes a string representation of a number and removes any trailing zeros
|
||||
* after the decimal point. If the number ends with a decimal point followed by only zeros,
|
||||
* the entire decimal portion is removed.
|
||||
*
|
||||
* @param num - The numeric string to process.
|
||||
* @returns The numeric string with trailing zeros removed.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* removeTrailingZeros("123.45000"); // Returns "123.45"
|
||||
* removeTrailingZeros("100.000"); // Returns "100"
|
||||
* removeTrailingZeros("0.0000"); // Returns "0"
|
||||
* ```
|
||||
*/
|
||||
function removeTrailingZeros(num: string): string {
|
||||
return num.replace(/(\.\d*?[1-9])0+|\.0*$/, "$1");
|
||||
}
|
||||
|
||||
@@ -17,3 +17,15 @@ export function getCurrencySymbol(locale: string | undefined, currency: string)
|
||||
.replace(/\d/g, "")
|
||||
.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",
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user