From 2162eef133f4a1d6dbcba7c68b9c65f47f25364b Mon Sep 17 00:00:00 2001
From: wolflu05 <76838159+wolflu05@users.noreply.github.com>
Date: Mon, 15 Jul 2024 00:06:48 +0200
Subject: [PATCH] add PUI qrcode preview
---
src/frontend/package.json | 3 +-
.../src/components/buttons/ButtonMenu.tsx | 2 +-
.../src/components/buttons/CopyButton.tsx | 44 ++++---
.../src/components/details/Details.tsx | 27 +---
.../src/components/items/ActionDropdown.tsx | 16 ++-
src/frontend/src/components/items/QRCode.tsx | 119 ++++++++++++++++++
src/frontend/src/enums/ApiEndpoints.tsx | 1 +
src/frontend/src/pages/build/BuildDetail.tsx | 5 +-
src/frontend/src/pages/part/PartDetail.tsx | 5 +-
.../pages/purchasing/PurchaseOrderDetail.tsx | 5 +-
.../src/pages/stock/LocationDetail.tsx | 39 +++---
src/frontend/src/pages/stock/StockDetail.tsx | 5 +-
src/frontend/src/tables/InvenTreeTable.tsx | 2 +-
src/frontend/yarn.lock | 50 +++++---
14 files changed, 240 insertions(+), 83 deletions(-)
create mode 100644 src/frontend/src/components/items/QRCode.tsx
diff --git a/src/frontend/package.json b/src/frontend/package.json
index 6abad83d32..d6df1680a8 100644
--- a/src/frontend/package.json
+++ b/src/frontend/package.json
@@ -38,7 +38,6 @@
"@mantine/spotlight": "^7.11.0",
"@mantine/vanilla-extract": "^7.11.0",
"@mdxeditor/editor": "^3.6.1",
- "@naisutech/react-tree": "^3.1.0",
"@sentry/react": "^8.13.0",
"@tabler/icons-react": "^3.7.0",
"@tanstack/react-query": "^5.49.2",
@@ -53,6 +52,7 @@
"embla-carousel-react": "^8.1.6",
"html5-qrcode": "^2.3.8",
"mantine-datatable": "^7.11.1",
+ "qrcode": "^1.5.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-grid-layout": "^1.4.4",
@@ -72,6 +72,7 @@
"@lingui/macro": "^4.11.1",
"@playwright/test": "^1.45.0",
"@types/node": "^20.14.9",
+ "@types/qrcode": "^1.5.5",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/react-grid-layout": "^1.3.5",
diff --git a/src/frontend/src/components/buttons/ButtonMenu.tsx b/src/frontend/src/components/buttons/ButtonMenu.tsx
index 42feeb0718..0bf2a36df7 100644
--- a/src/frontend/src/components/buttons/ButtonMenu.tsx
+++ b/src/frontend/src/components/buttons/ButtonMenu.tsx
@@ -11,7 +11,7 @@ export function ButtonMenu({
label = ''
}: {
icon: any;
- actions: any[];
+ actions: React.ReactNode[];
label?: string;
tooltip?: string;
}) {
diff --git a/src/frontend/src/components/buttons/CopyButton.tsx b/src/frontend/src/components/buttons/CopyButton.tsx
index 3ac02e5da2..c63ec29630 100644
--- a/src/frontend/src/components/buttons/CopyButton.tsx
+++ b/src/frontend/src/components/buttons/CopyButton.tsx
@@ -1,6 +1,13 @@
import { t } from '@lingui/macro';
-import { Button, CopyButton as MantineCopyButton } from '@mantine/core';
-import { IconCopy } from '@tabler/icons-react';
+import {
+ ActionIcon,
+ Button,
+ CopyButton as MantineCopyButton,
+ Text,
+ Tooltip
+} from '@mantine/core';
+
+import { InvenTreeIcon } from '../../functions/icons';
export function CopyButton({
value,
@@ -9,24 +16,27 @@ export function CopyButton({
value: any;
label?: JSX.Element;
}) {
+ const ButtonComponent = label ? Button : ActionIcon;
+
return (
{({ copied, copy }) => (
-
+
+
+ {copied ? (
+
+ ) : (
+
+ )}
+
+ {label && {label}}
+
+
)}
);
diff --git a/src/frontend/src/components/details/Details.tsx b/src/frontend/src/components/details/Details.tsx
index d3675bceaa..7c2346eb38 100644
--- a/src/frontend/src/components/details/Details.tsx
+++ b/src/frontend/src/components/details/Details.tsx
@@ -1,15 +1,12 @@
import { t } from '@lingui/macro';
import {
- ActionIcon,
Anchor,
Badge,
- CopyButton,
Paper,
Skeleton,
Stack,
Table,
- Text,
- Tooltip
+ Text
} from '@mantine/core';
import { useSuspenseQuery } from '@tanstack/react-query';
import { getValueAtPath } from 'mantine-datatable';
@@ -24,6 +21,7 @@ import { navigateToLink } from '../../functions/navigation';
import { getDetailUrl } from '../../functions/urls';
import { apiUrl } from '../../states/ApiState';
import { useGlobalSettingsState } from '../../states/SettingsState';
+import { CopyButton } from '../buttons/CopyButton';
import { YesNoButton } from '../buttons/YesNoButton';
import { ProgressBar } from '../items/ProgressBar';
import { StylishText } from '../items/StylishText';
@@ -325,26 +323,7 @@ function StatusValue(props: Readonly) {
}
function CopyField({ value }: { value: string }) {
- return (
-
- {({ copied, copy }) => (
-
-
- {copied ? (
-
- ) : (
-
- )}
-
-
- )}
-
- );
+ return ;
}
export function DetailsTableField({
diff --git a/src/frontend/src/components/items/ActionDropdown.tsx b/src/frontend/src/components/items/ActionDropdown.tsx
index 7d04fb0d56..d29c21c93f 100644
--- a/src/frontend/src/components/items/ActionDropdown.tsx
+++ b/src/frontend/src/components/items/ActionDropdown.tsx
@@ -6,6 +6,7 @@ import {
Menu,
Tooltip
} from '@mantine/core';
+import { modals } from '@mantine/modals';
import {
IconCopy,
IconEdit,
@@ -16,9 +17,11 @@ import {
} from '@tabler/icons-react';
import { ReactNode, useMemo } from 'react';
+import { ModelType } from '../../enums/ModelType';
import { identifierString } from '../../functions/conversion';
import { InvenTreeIcon } from '../../functions/icons';
import { notYetImplemented } from '../../functions/notifications';
+import { InvenTreeQRCode } from './QRCode';
export type ActionDropdownItem = {
icon: ReactNode;
@@ -128,11 +131,20 @@ export function BarcodeActionDropdown({
// Common action button for viewing a barcode
export function ViewBarcodeAction({
hidden = false,
- onClick
+ model,
+ pk
}: {
hidden?: boolean;
- onClick?: () => void;
+ model: ModelType;
+ pk: number;
}): ActionDropdownItem {
+ const onClick = () => {
+ modals.open({
+ title: t`View Barcode`,
+ children:
+ });
+ };
+
return {
icon: ,
name: t`View`,
diff --git a/src/frontend/src/components/items/QRCode.tsx b/src/frontend/src/components/items/QRCode.tsx
new file mode 100644
index 0000000000..b0826bd1d3
--- /dev/null
+++ b/src/frontend/src/components/items/QRCode.tsx
@@ -0,0 +1,119 @@
+import { t } from '@lingui/macro';
+import {
+ Box,
+ Code,
+ Group,
+ Image,
+ Select,
+ Skeleton,
+ Stack
+} from '@mantine/core';
+import { useQuery } from '@tanstack/react-query';
+import QR from 'qrcode';
+import { useEffect, useMemo, useState } from 'react';
+
+import { api } from '../../App';
+import { ApiEndpoints } from '../../enums/ApiEndpoints';
+import { ModelType } from '../../enums/ModelType';
+import { apiUrl } from '../../states/ApiState';
+import { useGlobalSettingsState } from '../../states/SettingsState';
+import { CopyButton } from '../buttons/CopyButton';
+
+type QRCodeProps = {
+ ecl?: 'L' | 'M' | 'Q' | 'H';
+ margin?: number;
+ data?: string;
+};
+
+export const QRCode = ({ data, ecl = 'Q', margin = 1 }: QRCodeProps) => {
+ const [qrCode, setQRCode] = useState();
+
+ useEffect(() => {
+ if (!data) return setQRCode(undefined);
+
+ QR.toString(data, { errorCorrectionLevel: ecl, type: 'svg', margin }).then(
+ (svg) => {
+ setQRCode(`data:image/svg+xml;utf8,${encodeURIComponent(svg)}`);
+ }
+ );
+ }, [data, ecl]);
+
+ return (
+
+ {qrCode ? (
+
+ ) : (
+
+ )}
+
+ );
+};
+
+type InvenTreeQRCodeProps = {
+ model: ModelType;
+ pk: number;
+ showEclSelector?: boolean;
+} & Omit;
+
+export const InvenTreeQRCode = ({
+ showEclSelector = true,
+ model,
+ pk,
+ ecl: eclProp = 'Q',
+ ...props
+}: InvenTreeQRCodeProps) => {
+ const settings = useGlobalSettingsState();
+ const [ecl, setEcl] = useState(eclProp);
+
+ useEffect(() => {
+ if (eclProp) setEcl(eclProp);
+ }, [eclProp]);
+
+ const { data } = useQuery({
+ queryKey: ['qr-code', model, pk],
+ queryFn: async () => {
+ const res = await api.post(apiUrl(ApiEndpoints.generate_barcode), {
+ model,
+ pk
+ });
+
+ return res.data?.barcode as string;
+ }
+ });
+
+ const eclOptions = useMemo(
+ () => [
+ { value: 'L', label: t`Low (7%)` },
+ { value: 'M', label: t`Medium (15%)` },
+ { value: 'Q', label: t`Quartile (25%)` },
+ { value: 'H', label: t`High (30%)` }
+ ],
+ []
+ );
+
+ return (
+
+
+ {data && settings.getSetting('BARCODE_SHOW_TEXT', 'false') && (
+
+ {showEclSelector && (
+
+ )}
+
+ );
+};
diff --git a/src/frontend/src/enums/ApiEndpoints.tsx b/src/frontend/src/enums/ApiEndpoints.tsx
index a3b49824e0..850c4506da 100644
--- a/src/frontend/src/enums/ApiEndpoints.tsx
+++ b/src/frontend/src/enums/ApiEndpoints.tsx
@@ -38,6 +38,7 @@ export enum ApiEndpoints {
settings_global_list = 'settings/global/',
settings_user_list = 'settings/user/',
barcode = 'barcode/',
+ generate_barcode = 'barcode/generate/',
news = 'news/',
global_status = 'generic/status/',
version = 'version/',
diff --git a/src/frontend/src/pages/build/BuildDetail.tsx b/src/frontend/src/pages/build/BuildDetail.tsx
index c5635d7278..87888c5655 100644
--- a/src/frontend/src/pages/build/BuildDetail.tsx
+++ b/src/frontend/src/pages/build/BuildDetail.tsx
@@ -370,7 +370,10 @@ export default function BuildDetail() {
tooltip={t`Barcode Actions`}
icon={}
actions={[
- ViewBarcodeAction({}),
+ ViewBarcodeAction({
+ model: ModelType.build,
+ pk: build.pk
+ }),
LinkBarcodeAction({
hidden: build?.barcode_hash
}),
diff --git a/src/frontend/src/pages/part/PartDetail.tsx b/src/frontend/src/pages/part/PartDetail.tsx
index 4c4dc156a9..f6587d06a4 100644
--- a/src/frontend/src/pages/part/PartDetail.tsx
+++ b/src/frontend/src/pages/part/PartDetail.tsx
@@ -894,7 +894,10 @@ export default function PartDetail() {
,
,
,
- ,
- tooltip: 'Scan items'
- },
- {
- name: 'Scan in container',
- icon: ,
- tooltip: 'Scan container'
- }
- ]}
- />,
+ location.pk ? (
+ ,
+ tooltip: 'Scan items'
+ },
+ {
+ name: 'Scan in container',
+ icon: ,
+ tooltip: 'Scan container'
+ }
+ ]}
+ />
+ ) : null,
,
= {
enableLabels?: boolean;
enableReports?: boolean;
pageSize?: number;
- barcodeActions?: any[];
+ barcodeActions?: React.ReactNode[];
tableFilters?: TableFilter[];
tableActions?: React.ReactNode[];
rowExpansion?: any;
diff --git a/src/frontend/yarn.lock b/src/frontend/yarn.lock
index 886fc793e7..a0ee7ef85f 100644
--- a/src/frontend/yarn.lock
+++ b/src/frontend/yarn.lock
@@ -796,7 +796,7 @@
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43"
integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==
-"@emotion/is-prop-valid@1.2.2", "@emotion/is-prop-valid@^1.2.0":
+"@emotion/is-prop-valid@1.2.2":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz#d4175076679c6a26faa92b03bb786f9e52612337"
integrity sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==
@@ -1822,15 +1822,6 @@
dependencies:
moo "^0.5.1"
-"@naisutech/react-tree@^3.1.0":
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/@naisutech/react-tree/-/react-tree-3.1.0.tgz#a83820425b53a1ec7a39804ff8bd9024f0a953f4"
- integrity sha512-6p1l3ZIaTmbgiAf/mpFELvqwl51LDhr+09f7L+C27DBLWjtleezCMoUuiSLhrJgpixCPNL13PuI3q2yn+0AGvA==
- dependencies:
- "@emotion/is-prop-valid" "^1.2.0"
- nanoid "^4.0.0"
- react-draggable "^4.4.5"
-
"@open-draft/deferred-promise@^2.1.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz#4a822d10f6f0e316be4d67b4d4f8c9a124b073bd"
@@ -2598,6 +2589,13 @@
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6"
integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==
+"@types/qrcode@^1.5.5":
+ version "1.5.5"
+ resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.5.5.tgz#993ff7c6b584277eee7aac0a20861eab682f9dac"
+ integrity sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==
+ dependencies:
+ "@types/node" "*"
+
"@types/react-dom@^18.3.0":
version "18.3.0"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.0.tgz#0cbc818755d87066ab6ca74fbedb2547d74a82b0"
@@ -3455,6 +3453,11 @@ diff@^5.1.0:
resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531"
integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==
+dijkstrajs@^1.0.1:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz#4c8dbdea1f0f6478bff94d9c49c784d623e4fc23"
+ integrity sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==
+
dom-helpers@^5.0.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
@@ -3507,6 +3510,11 @@ emoji-regex@^8.0.0:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+encode-utf8@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda"
+ integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==
+
error-ex@^1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
@@ -4952,11 +4960,6 @@ nanoid@^3.3.7:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
-nanoid@^4.0.0:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-4.0.2.tgz#140b3c5003959adbebf521c170f282c5e7f9fb9e"
- integrity sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==
-
next-tick@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb"
@@ -5236,6 +5239,11 @@ playwright@1.45.0:
optionalDependencies:
fsevents "2.3.2"
+pngjs@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb"
+ integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==
+
pofile@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/pofile/-/pofile-1.1.4.tgz#eab7e29f5017589b2a61b2259dff608c0cad76a2"
@@ -5302,6 +5310,16 @@ punycode@^2.1.0:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
+qrcode@^1.5.3:
+ version "1.5.3"
+ resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.3.tgz#03afa80912c0dccf12bc93f615a535aad1066170"
+ integrity sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==
+ dependencies:
+ dijkstrajs "^1.0.1"
+ encode-utf8 "^1.0.3"
+ pngjs "^5.0.0"
+ yargs "^15.3.1"
+
ramda@^0.27.1:
version "0.27.2"
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.2.tgz#84463226f7f36dc33592f6f4ed6374c48306c3f1"
@@ -6280,7 +6298,7 @@ yargs-parser@^18.1.2:
camelcase "^5.0.0"
decamelize "^1.2.0"
-yargs@^15.0.2:
+yargs@^15.0.2, yargs@^15.3.1:
version "15.4.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==