2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-11-08 00:55:41 +00:00

Merge branch 'master' of https://github.com/inventree/InvenTree into matmair/issue6281

This commit is contained in:
Matthias Mair
2025-01-08 08:03:18 +01:00
209 changed files with 6775 additions and 6583 deletions

View File

@@ -40,7 +40,10 @@ export default defineConfig({
env: {
INVENTREE_DEBUG: 'True',
INVENTREE_PLUGINS_ENABLED: 'True',
INVENTREE_ADMIN_URL: 'test-admin'
INVENTREE_ADMIN_URL: 'test-admin',
INVENTREE_SITE_URL: 'http://localhost:8000',
INVENTREE_CORS_ORIGIN_ALLOW_ALL: 'True',
INVENTREE_COOKIE_SAMESITE: 'Lax'
},
url: 'http://127.0.0.1:8000/api/',
reuseExistingServer: !process.env.CI,

View File

@@ -27,8 +27,7 @@ export function Boundary({
}>): ReactNode {
const onError = useCallback(
(error: unknown, componentStack: string | undefined, eventId: string) => {
console.error(`Error rendering component: ${label}`);
console.error(error, componentStack);
console.error(`ERR: Error rendering component: ${label}`);
},
[]
);

View File

@@ -4,7 +4,7 @@ import { useQuery } from '@tanstack/react-query';
import { type ReactNode, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { api } from '../../../App';
import { useApi } from '../../../contexts/ApiContext';
import type { ModelType } from '../../../enums/ModelType';
import {
InvenTreeIcon,
@@ -31,6 +31,7 @@ function QueryCountWidget({
icon?: InvenTreeIconType;
params: any;
}>): ReactNode {
const api = useApi();
const user = useUserState();
const navigate = useNavigate();

View File

@@ -14,7 +14,7 @@ import { getValueAtPath } from 'mantine-datatable';
import { useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { api } from '../../App';
import { useApi } from '../../contexts/ApiContext';
import { formatDate } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import type { ModelType } from '../../enums/ModelType';
@@ -101,6 +101,8 @@ function NameBadge({
pk,
type
}: Readonly<{ pk: string | number; type: BadgeType }>) {
const api = useApi();
const { data } = useQuery({
queryKey: ['badge', type, pk],
queryFn: async () => {
@@ -217,6 +219,7 @@ function BooleanValue(props: Readonly<FieldProps>) {
}
function TableAnchorValue(props: Readonly<FieldProps>) {
const api = useApi();
const navigate = useNavigate();
const { data } = useQuery({

View File

@@ -7,7 +7,7 @@ import 'easymde/dist/easymde.min.css';
import { useCallback, useEffect, useMemo, useState } from 'react';
import SimpleMDE from 'react-simplemde-editor';
import { api } from '../../App';
import { useApi } from '../../contexts/ApiContext';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import type { ModelType } from '../../enums/ModelType';
import { apiUrl } from '../../states/ApiState';
@@ -31,6 +31,7 @@ export default function NotesEditor({
modelId: number;
editable?: boolean;
}>) {
const api = useApi();
// In addition to the editable prop, we also need to check if the user has "enabled" editing
const [editing, setEditing] = useState<boolean>(false);

View File

@@ -12,7 +12,7 @@ import {
} from '@mantine/core';
import { useId } from '@mantine/hooks';
import { notifications } from '@mantine/notifications';
import { useQuery } from '@tanstack/react-query';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useCallback, useEffect, useMemo, useState } from 'react';
import {
type FieldValues,
@@ -23,7 +23,7 @@ import {
} from 'react-hook-form';
import { useNavigate } from 'react-router-dom';
import { api, queryClient } from '../../App';
import { useApi } from '../../contexts/ApiContext';
import type { ApiEndpoints } from '../../enums/ApiEndpoints';
import type { ModelType } from '../../enums/ModelType';
import {
@@ -110,6 +110,8 @@ export function OptionsApiForm({
props: ApiFormProps;
id?: string;
}>) {
const api = useApi();
const props = useMemo(
() => ({
..._props,
@@ -205,6 +207,8 @@ export function ApiForm({
props: ApiFormProps;
optionsLoading: boolean;
}>) {
const api = useApi();
const queryClient = useQueryClient();
const navigate = useNavigate();
const [fields, setFields] = useState<ApiFormFieldSet>(

View File

@@ -5,7 +5,7 @@ import {
useFormContext
} from 'react-hook-form';
import { api } from '../../../App';
import { useApi } from '../../../contexts/ApiContext';
import {
constructField,
extractAvailableFields
@@ -29,6 +29,7 @@ export function DependentField({
url?: string;
setFields?: React.Dispatch<React.SetStateAction<ApiFormFieldSet>>;
}>) {
const api = useApi();
const { watch, resetField } = useFormContext();
const mappedFieldNames = useMemo(

View File

@@ -15,7 +15,7 @@ import {
} from 'react-hook-form';
import Select from 'react-select';
import { api } from '../../../App';
import { useApi } from '../../../contexts/ApiContext';
import { vars } from '../../../theme';
import { RenderInstance } from '../../render/Instance';
import type { ApiFormFieldType } from './ApiFormField';
@@ -34,6 +34,7 @@ export function RelatedModelField({
fieldName: string;
limit?: number;
}>) {
const api = useApi();
const fieldId = useId();
const {
field,

View File

@@ -9,7 +9,7 @@ import {
} from '@tabler/icons-react';
import { type ReactNode, useCallback, useMemo, useState } from 'react';
import { api } from '../../App';
import { useApi } from '../../contexts/ApiContext';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { cancelEvent } from '../../functions/events';
import {
@@ -131,6 +131,7 @@ export default function ImporterDataSelector({
}: Readonly<{
session: ImportSessionState;
}>) {
const api = useApi();
const table = useTable('dataimporter');
const [selectedFieldNames, setSelectedFieldNames] = useState<string[]>([]);

View File

@@ -13,7 +13,7 @@ import {
import { IconCheck } from '@tabler/icons-react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { api } from '../../App';
import { useApi } from '../../contexts/ApiContext';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import type { ImportSessionState } from '../../hooks/UseImportSession';
import { apiUrl } from '../../states/ApiState';
@@ -24,6 +24,8 @@ function ImporterColumn({
column,
options
}: Readonly<{ column: any; options: any[] }>) {
const api = useApi();
const [errorMessage, setErrorMessage] = useState<string>('');
const [selectedColumn, setSelectedColumn] = useState<string>(
@@ -78,6 +80,8 @@ function ImporterDefaultField({
fieldName: string;
session: ImportSessionState;
}) {
const api = useApi();
const onChange = useCallback(
(value: any) => {
// Update the default value for the field
@@ -162,6 +166,8 @@ export default function ImporterColumnSelector({
}: Readonly<{
session: ImportSessionState;
}>) {
const api = useApi();
const [errorMessage, setErrorMessage] = useState<string>('');
const acceptMapping = useCallback(() => {

View File

@@ -21,7 +21,7 @@ import { useQuery } from '@tanstack/react-query';
import { useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { api } from '../../App';
import { useApi } from '../../contexts/ApiContext';
import type { ApiEndpoints } from '../../enums/ApiEndpoints';
import type { ModelType } from '../../enums/ModelType';
import { navigateToLink } from '../../functions/navigation';
@@ -48,6 +48,7 @@ export default function NavigationTree({
modelType: ModelType;
endpoint: ApiEndpoints;
}>) {
const api = useApi();
const navigate = useNavigate();
const treeState = useTree();

View File

@@ -3,7 +3,7 @@ import { Alert, Anchor, Group, Skeleton, Space, Text } from '@mantine/core';
import { useQuery } from '@tanstack/react-query';
import { type ReactNode, useCallback } from 'react';
import { api } from '../../App';
import { useApi } from '../../contexts/ApiContext';
import { ModelType } from '../../enums/ModelType';
import { navigateToLink } from '../../functions/navigation';
import { shortenString } from '../../functions/tables';
@@ -130,6 +130,8 @@ export function RenderRemoteInstance({
model: ModelType;
pk: number;
}>): ReactNode {
const api = useApi();
const { data, isLoading, isFetching } = useQuery({
queryKey: ['model', model, pk],
queryFn: async () => {

View File

@@ -10,7 +10,7 @@ import React, {
} from 'react';
import { useStore } from 'zustand';
import { api } from '../../App';
import { useApi } from '../../contexts/ApiContext';
import type { ModelType } from '../../enums/ModelType';
import { useEditApiFormModal } from '../../hooks/UseForm';
import { apiUrl } from '../../states/ApiState';
@@ -40,6 +40,8 @@ export function SettingList({
settingsState.fetchSettings();
}, []);
const api = useApi();
const allKeys = useMemo(
() => settingsState?.settings?.map((s) => s.key) ?? [],
[settingsState?.settings]

View File

@@ -0,0 +1,31 @@
import { type QueryClient, QueryClientProvider } from '@tanstack/react-query';
import type { AxiosInstance } from 'axios';
import { createContext, useContext } from 'react';
const ApiContext = createContext<AxiosInstance | null>(null);
export const ApiProvider = ({
api,
client,
children
}: {
api: AxiosInstance;
client: QueryClient;
children: React.ReactNode;
}) => {
return (
<QueryClientProvider client={client}>
<ApiContext.Provider value={api}>{children}</ApiContext.Provider>
</QueryClientProvider>
);
};
export const useApi = () => {
const context = useContext(ApiContext);
if (!context) {
throw new Error('useApi must be used within an ApiProvider');
}
return context;
};

View File

@@ -1,12 +0,0 @@
import { QueryClientProvider } from '@tanstack/react-query';
import { queryClient } from '../App';
import { ThemeContext } from './ThemeContext';
export const BaseContext = ({ children }: { children: any }) => {
return (
<QueryClientProvider client={queryClient}>
<ThemeContext>{children}</ThemeContext>
</QueryClientProvider>
);
};

View File

@@ -2,8 +2,8 @@ import { t } from '@lingui/macro';
import { IconPackages } from '@tabler/icons-react';
import { useMemo, useState } from 'react';
import { api } from '../App';
import type { ApiFormFieldSet } from '../components/forms/fields/ApiFormField';
import { useApi } from '../contexts/ApiContext';
import { ApiEndpoints } from '../enums/ApiEndpoints';
import { apiUrl } from '../states/ApiState';
import { useGlobalSettingsState } from '../states/SettingsState';
@@ -179,6 +179,8 @@ export function usePartParameterFields({
}: {
editTemplate?: boolean;
}): ApiFormFieldSet {
const api = useApi();
// Valid field choices
const [choices, setChoices] = useState<any[]>([]);

View File

@@ -5,7 +5,7 @@
import { useQuery } from '@tanstack/react-query';
import { useCallback, useMemo } from 'react';
import { api } from '../App';
import { useApi } from '../contexts/ApiContext';
import { ApiEndpoints } from '../enums/ApiEndpoints';
import { resolveItem } from '../functions/conversion';
import { apiUrl } from '../states/ApiState';
@@ -20,6 +20,8 @@ type UseFilterProps = {
};
export function useFilters(props: UseFilterProps) {
const api = useApi();
const query = useQuery({
enabled: true,
gcTime: 500,

View File

@@ -1,7 +1,7 @@
import { type QueryObserverResult, useQuery } from '@tanstack/react-query';
import { useCallback, useMemo, useState } from 'react';
import { api } from '../App';
import { useApi } from '../contexts/ApiContext';
import type { ApiEndpoints } from '../enums/ApiEndpoints';
import { type PathParams, apiUrl } from '../states/ApiState';
@@ -48,6 +48,8 @@ export function useInstance<T = any>({
throwError?: boolean;
updateInterval?: number;
}): UseInstanceResult {
const api = useApi();
const [instance, setInstance] = useState<T | undefined>(defaultValue);
const [requestStatus, setRequestStatus] = useState<number>(0);

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: es_MX\n"
"Project-Id-Version: inventree\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-12-31 04:52\n"
"PO-Revision-Date: 2025-01-05 01:53\n"
"Last-Translator: \n"
"Language-Team: Spanish, Mexico\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,6 @@ import ReactDOM from 'react-dom/client';
import 'react-grid-layout/css/styles.css';
import 'react-resizable/css/styles.css';
import { api } from './App';
import type { HostList } from './states/states';
import MainView from './views/MainView';
@@ -27,7 +26,6 @@ declare global {
sentry_dsn?: string;
environment?: string;
};
InvenTreeAPI: typeof api;
React: typeof React;
}
}
@@ -105,4 +103,3 @@ if (window.location.pathname === '/') {
}
window.React = React;
window.InvenTreeAPI = api;

View File

@@ -56,7 +56,7 @@ export default function Scan() {
// Prevent duplicates
if (history.find((i) => i.model == item.model && i.pk == item.pk)) {
showNotification({
label: t`Duplicate`,
title: t`Duplicate`,
message: t`Item already scanned`,
color: 'orange'
});

View File

@@ -42,6 +42,7 @@ export function CurrencyTable({
.then(() => {
table.refreshTable();
showNotification({
title: t`Success`,
message: t`Exchange rates updated`,
color: 'green'
});

View File

@@ -11,16 +11,17 @@ import {
} from '@tabler/icons-react';
import { useCallback, useMemo } from 'react';
import { api } from '../App';
import { ActionButton } from '../components/buttons/ActionButton';
import { PageDetail } from '../components/nav/PageDetail';
import { PanelGroup } from '../components/panels/PanelGroup';
import { useApi } from '../contexts/ApiContext';
import { ApiEndpoints } from '../enums/ApiEndpoints';
import { useTable } from '../hooks/UseTable';
import { apiUrl } from '../states/ApiState';
import { NotificationTable } from '../tables/notifications/NotificationTable';
export default function NotificationsPage() {
const api = useApi();
const unreadTable = useTable('unreadnotifications');
const readTable = useTable('readnotifications');

View File

@@ -13,7 +13,7 @@ import {
IconUsersGroup
} from '@tabler/icons-react';
import { type ReactNode, useMemo } from 'react';
import { useParams } from 'react-router-dom';
import { useNavigate, useParams } from 'react-router-dom';
import AdminButton from '../../components/buttons/AdminButton';
import {
@@ -66,6 +66,7 @@ export type CompanyDetailProps = {
export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
const { id } = useParams();
const navigate = useNavigate();
const user = useUserState();
const {
@@ -283,7 +284,9 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
url: ApiEndpoints.company_list,
pk: company?.pk,
title: t`Delete Company`,
onFormSuccess: refreshInstance
onFormSuccess: () => {
navigate('/');
}
});
const companyActions = useMemo(() => {

View File

@@ -34,7 +34,6 @@ import { type ReactNode, useMemo, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import Select from 'react-select';
import { api } from '../../App';
import AdminButton from '../../components/buttons/AdminButton';
import { PrintingActions } from '../../components/buttons/PrintingActions';
import {
@@ -63,6 +62,7 @@ import type { PanelType } from '../../components/panels/Panel';
import { PanelGroup } from '../../components/panels/PanelGroup';
import { RenderPart } from '../../components/render/Part';
import OrderPartsWizard from '../../components/wizards/OrderPartsWizard';
import { useApi } from '../../contexts/ApiContext';
import { formatPriceRange } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
@@ -110,6 +110,7 @@ import PartSupplierDetail from './PartSupplierDetail';
export default function PartDetail() {
const { id } = useParams();
const api = useApi();
const navigate = useNavigate();
const user = useUserState();

View File

@@ -217,6 +217,13 @@ export default function PurchaseOrderDetail() {
icon: 'reference',
copy: true,
hidden: !order.project_code
},
{
type: 'text',
name: 'responsible',
label: t`Responsible`,
badge: 'owner',
hidden: !order.responsible
}
];
@@ -225,6 +232,7 @@ export default function PurchaseOrderDetail() {
type: 'date',
name: 'creation_date',
label: t`Creation Date`,
copy: true,
icon: 'calendar'
},
{
@@ -240,6 +248,7 @@ export default function PurchaseOrderDetail() {
name: 'target_date',
label: t`Target Date`,
icon: 'calendar',
copy: true,
hidden: !order.target_date
},
{
@@ -249,13 +258,6 @@ export default function PurchaseOrderDetail() {
label: t`Completion Date`,
copy: true,
hidden: !order.complete_date
},
{
type: 'text',
name: 'responsible',
label: t`Responsible`,
badge: 'owner',
hidden: !order.responsible
}
];

View File

@@ -187,6 +187,13 @@ export default function ReturnOrderDetail() {
icon: 'reference',
copy: true,
hidden: !order.project_code
},
{
type: 'text',
name: 'responsible',
label: t`Responsible`,
badge: 'owner',
hidden: !order.responsible
}
];
@@ -221,13 +228,6 @@ export default function ReturnOrderDetail() {
label: t`Completion Date`,
copy: true,
hidden: !order.complete_date
},
{
type: 'text',
name: 'responsible',
label: t`Responsible`,
badge: 'owner',
hidden: !order.responsible
}
];

View File

@@ -199,6 +199,13 @@ export default function SalesOrderDetail() {
icon: 'reference',
copy: true,
hidden: !order.project_code
},
{
type: 'text',
name: 'responsible',
label: t`Responsible`,
badge: 'owner',
hidden: !order.responsible
}
];
@@ -231,13 +238,6 @@ export default function SalesOrderDetail() {
label: t`Completion Date`,
hidden: !order.shipment_date,
copy: true
},
{
type: 'text',
name: 'responsible',
label: t`Responsible`,
badge: 'owner',
hidden: !order.responsible
}
];

View File

@@ -14,7 +14,6 @@ import { useQuery } from '@tanstack/react-query';
import { type ReactNode, useMemo, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { api } from '../../App';
import AdminButton from '../../components/buttons/AdminButton';
import { PrintingActions } from '../../components/buttons/PrintingActions';
import {
@@ -43,6 +42,7 @@ import { PanelGroup } from '../../components/panels/PanelGroup';
import LocateItemButton from '../../components/plugins/LocateItemButton';
import { StatusRenderer } from '../../components/render/StatusRenderer';
import OrderPartsWizard from '../../components/wizards/OrderPartsWizard';
import { useApi } from '../../contexts/ApiContext';
import { formatCurrency } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
@@ -78,6 +78,7 @@ import { StockTrackingTable } from '../../tables/stock/StockTrackingTable';
export default function StockDetail() {
const { id } = useParams();
const api = useApi();
const user = useUserState();
const globalSettings = useGlobalSettingsState();
@@ -377,7 +378,7 @@ export default function StockDetail() {
); // Must not be installed into another item
}, [stockitem]);
const showSalesAlloctions: boolean = useMemo(() => {
const showSalesAllocations: boolean = useMemo(() => {
return stockitem?.part_detail?.salable;
}, [stockitem]);
@@ -452,14 +453,14 @@ export default function StockDetail() {
icon: <IconBookmark />,
hidden:
!stockitem.in_stock ||
(!showSalesAlloctions && !showBuildAllocations),
(!showSalesAllocations && !showBuildAllocations),
content: (
<Accordion
multiple={true}
defaultValue={['buildallocations', 'salesallocations']}
defaultValue={['buildAllocations', 'salesAllocations']}
>
{showBuildAllocations && (
<Accordion.Item value='buildallocations' key='buildallocations'>
<Accordion.Item value='buildAllocations' key='buildAllocations'>
<Accordion.Control>
<StylishText size='lg'>{t`Build Order Allocations`}</StylishText>
</Accordion.Control>
@@ -473,8 +474,8 @@ export default function StockDetail() {
</Accordion.Panel>
</Accordion.Item>
)}
{showSalesAlloctions && (
<Accordion.Item value='salesallocations' key='salesallocations'>
{showSalesAllocations && (
<Accordion.Item value='salesAllocations' key='salesAllocations'>
<Accordion.Control>
<StylishText size='lg'>{t`Sales Order Allocations`}</StylishText>
</Accordion.Control>
@@ -536,7 +537,7 @@ export default function StockDetail() {
})
];
}, [
showSalesAlloctions,
showSalesAllocations,
showBuildAllocations,
showInstalledItems,
stockitem,

View File

@@ -36,7 +36,18 @@ export const useIconState = create<IconState>()((set, get) => ({
fetchIcons: async () => {
if (get().hasLoaded) return;
const packs = await api.get(apiUrl(ApiEndpoints.icons));
const packs = await api.get(apiUrl(ApiEndpoints.icons)).catch((_error) => {
console.error('ERR: Could not fetch icon packages');
showNotification({
title: t`Error`,
message: t`Error loading icon package from server`,
color: 'red'
});
});
if (!packs) {
return;
}
await Promise.all(
packs.data.map(async (pack: any) => {

View File

@@ -9,7 +9,7 @@ import { YesNoButton } from '../components/buttons/YesNoButton';
import { Thumbnail } from '../components/images/Thumbnail';
import { ProgressBar } from '../components/items/ProgressBar';
import { TableStatusRenderer } from '../components/render/StatusRenderer';
import { RenderOwner } from '../components/render/User';
import { RenderOwner, RenderUser } from '../components/render/User';
import { formatCurrency, formatDate } from '../defaults/formatters';
import type { ModelType } from '../enums/ModelType';
import { resolveItem } from '../functions/conversion';
@@ -202,6 +202,18 @@ export function StatusColumn({
};
}
export function CreatedByColumn(props: TableColumnProps): TableColumn {
return {
accessor: 'created_by',
title: t`Created By`,
sortable: true,
switchable: true,
render: (record: any) =>
record.created_by && RenderUser({ instance: record.created_by }),
...props
};
}
export function ResponsibleColumn(props: TableColumnProps): TableColumn {
return {
accessor: 'responsible',

View File

@@ -205,3 +205,47 @@ export function HasProjectCodeFilter(): TableFilter {
description: t`Show orders with an assigned project code`
};
}
export function OrderStatusFilter({
model
}: { model: ModelType }): TableFilter {
return {
name: 'status',
label: t`Status`,
description: t`Filter by order status`,
choiceFunction: StatusFilterOptions(model)
};
}
export function ProjectCodeFilter({
choices
}: { choices: TableFilterChoice[] }): TableFilter {
return {
name: 'project_code',
label: t`Project Code`,
description: t`Filter by project code`,
choices: choices
};
}
export function ResponsibleFilter({
choices
}: { choices: TableFilterChoice[] }): TableFilter {
return {
name: 'assigned_to',
label: t`Responsible`,
description: t`Filter by responsible owner`,
choices: choices
};
}
export function CreatedByFilter({
choices
}: { choices: TableFilterChoice[] }): TableFilter {
return {
name: 'created_by',
label: t`Created By`,
description: t`Filter by user who created the order`,
choices: choices
};
}

View File

@@ -13,9 +13,9 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { showNotification } from '@mantine/notifications';
import { api } from '../App';
import { Boundary } from '../components/Boundary';
import type { ApiFormFieldSet } from '../components/forms/fields/ApiFormField';
import { useApi } from '../contexts/ApiContext';
import type { ModelType } from '../enums/ModelType';
import { resolveItem } from '../functions/conversion';
import { cancelEvent } from '../functions/events';
@@ -139,6 +139,7 @@ export function InvenTreeTable<T extends Record<string, any>>({
const [fieldNames, setFieldNames] = useState<Record<string, string>>({});
const api = useApi();
const navigate = useNavigate();
const { showContextMenu } = useContextMenu();

View File

@@ -16,11 +16,11 @@ import {
import { useState } from 'react';
import { Fragment } from 'react/jsx-runtime';
import { api } from '../App';
import { Boundary } from '../components/Boundary';
import { ActionButton } from '../components/buttons/ActionButton';
import { ButtonMenu } from '../components/buttons/ButtonMenu';
import { PrintingActions } from '../components/buttons/PrintingActions';
import { useApi } from '../contexts/ApiContext';
import { useDeleteApiFormModal } from '../hooks/UseForm';
import type { TableState } from '../hooks/UseTable';
import { TableColumnSelect } from './ColumnSelect';
@@ -50,6 +50,8 @@ export default function InvenTreeTableHeader({
filters: TableFilter[];
toggleColumn: (column: string) => void;
}>) {
const api = useApi();
// Filter list visibility
const [filtersVisible, setFiltersVisible] = useState<boolean>(false);

View File

@@ -11,12 +11,12 @@ import {
import { type ReactNode, useCallback, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { api } from '../../App';
import { ActionButton } from '../../components/buttons/ActionButton';
import { AddItemButton } from '../../components/buttons/AddItemButton';
import { YesNoButton } from '../../components/buttons/YesNoButton';
import { Thumbnail } from '../../components/images/Thumbnail';
import ImporterDrawer from '../../components/importer/ImporterDrawer';
import { useApi } from '../../contexts/ApiContext';
import { formatDecimal, formatPriceRange } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
@@ -71,6 +71,7 @@ export function BomTable({
partLocked?: boolean;
params?: any;
}>) {
const api = useApi();
const user = useUserState();
const table = useTable('bom');
const navigate = useNavigate();

View File

@@ -8,7 +8,11 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { useBuildOrderFields } from '../../forms/BuildForms';
import { useOwnerFilters, useProjectCodeFilters } from '../../hooks/UseFilter';
import {
useOwnerFilters,
useProjectCodeFilters,
useUserFilters
} from '../../hooks/UseFilter';
import { useCreateApiFormModal } from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable';
import { apiUrl } from '../../states/ApiState';
@@ -32,8 +36,11 @@ import {
HasProjectCodeFilter,
MaxDateFilter,
MinDateFilter,
OrderStatusFilter,
OutstandingFilter,
OverdueFilter,
StatusFilterOptions,
ProjectCodeFilter,
ResponsibleFilter,
type TableFilter,
TargetDateAfterFilter,
TargetDateBeforeFilter
@@ -117,21 +124,12 @@ export function BuildOrderTable({
const projectCodeFilters = useProjectCodeFilters();
const ownerFilters = useOwnerFilters();
const userFilters = useUserFilters();
const tableFilters: TableFilter[] = useMemo(() => {
const filters: TableFilter[] = [
{
name: 'outstanding',
type: 'boolean',
label: t`Outstanding`,
description: t`Show outstanding orders`
},
{
name: 'status',
label: t`Status`,
description: t`Filter by order status`,
choiceFunction: StatusFilterOptions(ModelType.build)
},
OutstandingFilter(),
OrderStatusFilter({ model: ModelType.build }),
OverdueFilter(),
AssignedToMeFilter(),
MinDateFilter(),
@@ -142,25 +140,15 @@ export function BuildOrderTable({
TargetDateAfterFilter(),
CompletedBeforeFilter(),
CompletedAfterFilter(),
{
name: 'project_code',
label: t`Project Code`,
description: t`Filter by project code`,
choices: projectCodeFilters.choices
},
ProjectCodeFilter({ choices: projectCodeFilters.choices }),
HasProjectCodeFilter(),
{
name: 'issued_by',
label: t`Issued By`,
description: t`Filter by user who issued this order`,
choices: ownerFilters.choices
choices: userFilters.choices
},
{
name: 'assigned_to',
label: t`Responsible`,
description: t`Filter by responsible owner`,
choices: ownerFilters.choices
}
ResponsibleFilter({ choices: ownerFilters.choices })
];
// If we are filtering on a specific part, we can include the "include variants" filter
@@ -174,7 +162,12 @@ export function BuildOrderTable({
}
return filters;
}, [partId, projectCodeFilters.choices, ownerFilters.choices]);
}, [
partId,
projectCodeFilters.choices,
ownerFilters.choices,
userFilters.choices
]);
const user = useUserState();

View File

@@ -10,10 +10,10 @@ import {
useState
} from 'react';
import { api } from '../../App';
import { PassFailButton } from '../../components/buttons/YesNoButton';
import type { ApiFormFieldSet } from '../../components/forms/fields/ApiFormField';
import { RenderUser } from '../../components/render/User';
import { useApi } from '../../contexts/ApiContext';
import { formatDate } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { useTestResultFields } from '../../forms/StockForms';
@@ -40,6 +40,7 @@ export default function BuildOrderTestTable({
}>) {
const table = useTable('build-tests');
const user = useUserState();
const api = useApi();
// Fetch the test templates required for this build order
const { data: testTemplates } = useQuery({

View File

@@ -18,11 +18,11 @@ import { useQuery } from '@tanstack/react-query';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { api } from '../../App';
import { ActionButton } from '../../components/buttons/ActionButton';
import { AddItemButton } from '../../components/buttons/AddItemButton';
import { ProgressBar } from '../../components/items/ProgressBar';
import { StylishText } from '../../components/items/StylishText';
import { useApi } from '../../contexts/ApiContext';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
@@ -121,6 +121,7 @@ export default function BuildOutputTable({
build,
refreshBuild
}: Readonly<{ build: any; refreshBuild: () => void }>) {
const api = useApi();
const user = useUserState();
const navigate = useNavigate();
const table = useTable('build-outputs');

View File

@@ -10,10 +10,10 @@ import {
} from '@tabler/icons-react';
import { type ReactNode, useCallback, useMemo, useState } from 'react';
import { api } from '../../App';
import { ActionButton } from '../../components/buttons/ActionButton';
import type { ApiFormFieldSet } from '../../components/forms/fields/ApiFormField';
import { AttachmentLink } from '../../components/items/AttachmentLink';
import { useApi } from '../../contexts/ApiContext';
import { formatFileSize } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import type { ModelType } from '../../enums/ModelType';
@@ -99,6 +99,7 @@ export function AttachmentTable({
model_type: ModelType;
model_id: number;
}>): ReactNode {
const api = useApi();
const user = useUserState();
const table = useTable(`${model_type}-attachments`);

View File

@@ -20,7 +20,6 @@ import { useQuery } from '@tanstack/react-query';
import { useCallback, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { api } from '../../App';
import { AddItemButton } from '../../components/buttons/AddItemButton';
import { YesNoButton } from '../../components/buttons/YesNoButton';
import {
@@ -40,6 +39,7 @@ import {
TableStatusRenderer
} from '../../components/render/StatusRenderer';
import { MachineSettingList } from '../../components/settings/SettingList';
import { useApi } from '../../contexts/ApiContext';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import {
useCreateApiFormModal,
@@ -103,6 +103,8 @@ export function useMachineTypeDriver({
includeTypes = true,
includeDrivers = true
}: { includeTypes?: boolean; includeDrivers?: boolean } = {}) {
const api = useApi();
const {
data: machineTypes,
isFetching: isMachineTypesFetching,
@@ -146,6 +148,7 @@ function MachineDrawer({
machinePk: string;
refreshTable: () => void;
}>) {
const api = useApi();
const navigate = useNavigate();
const {
data: machine,

View File

@@ -5,9 +5,9 @@ import { useQuery } from '@tanstack/react-query';
import { type ReactNode, useCallback, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { api } from '../../App';
import { YesNoButton } from '../../components/buttons/YesNoButton';
import type { ApiFormFieldSet } from '../../components/forms/fields/ApiFormField';
import { useApi } from '../../contexts/ApiContext';
import { formatDecimal } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
@@ -95,11 +95,12 @@ export default function ParametricPartTable({
}: Readonly<{
categoryId?: any;
}>) {
const api = useApi();
const table = useTable('parametric-parts');
const user = useUserState();
const navigate = useNavigate();
const categoryParmeters = useQuery({
const categoryParameters = useQuery({
queryKey: ['category-parameters', categoryId],
queryFn: async () => {
return api
@@ -170,13 +171,13 @@ export default function ParametricPartTable({
records[partIndex].parameters[parameterIndex] = parameter;
}
table.setRecords(records);
table.updateRecord(records[partIndex]);
},
[table.records]
[table.updateRecord]
);
const parameterColumns: TableColumn[] = useMemo(() => {
const data = categoryParmeters.data ?? [];
const data = categoryParameters.data ?? [];
return data.map((template: any) => {
let title = template.name;
@@ -201,7 +202,7 @@ export default function ParametricPartTable({
)
};
});
}, [user, categoryParmeters.data]);
}, [user, categoryParameters.data]);
const onParameterClick = useCallback((template: number, part: any) => {
setSelectedTemplate(template);

View File

@@ -13,12 +13,12 @@ import {
import { useCallback, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { api } from '../../App';
import { ActionButton } from '../../components/buttons/ActionButton';
import { YesNoButton } from '../../components/buttons/YesNoButton';
import { DetailDrawer } from '../../components/nav/DetailDrawer';
import PluginDrawer from '../../components/plugins/PluginDrawer';
import type { PluginInterface } from '../../components/plugins/PluginInterface';
import { useApi } from '../../contexts/ApiContext';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import {
useCreateApiFormModal,
@@ -63,6 +63,7 @@ function PluginIcon({ plugin }: Readonly<{ plugin: PluginInterface }>) {
* Table displaying list of available plugins
*/
export default function PluginListTable() {
const api = useApi();
const table = useTable('plugin');
const navigate = useNavigate();
const user = useUserState();

View File

@@ -8,13 +8,18 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms';
import { useOwnerFilters, useProjectCodeFilters } from '../../hooks/UseFilter';
import {
useOwnerFilters,
useProjectCodeFilters,
useUserFilters
} from '../../hooks/UseFilter';
import { useCreateApiFormModal } from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable';
import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState';
import {
CompletionDateColumn,
CreatedByColumn,
CreationDateColumn,
DescriptionColumn,
LineItemsProgressColumn,
@@ -30,12 +35,15 @@ import {
CompletedBeforeFilter,
CreatedAfterFilter,
CreatedBeforeFilter,
CreatedByFilter,
HasProjectCodeFilter,
MaxDateFilter,
MinDateFilter,
OrderStatusFilter,
OutstandingFilter,
OverdueFilter,
StatusFilterOptions,
ProjectCodeFilter,
ResponsibleFilter,
type TableFilter,
TargetDateAfterFilter,
TargetDateBeforeFilter
@@ -57,15 +65,11 @@ export function PurchaseOrderTable({
const projectCodeFilters = useProjectCodeFilters();
const responsibleFilters = useOwnerFilters();
const createdByFilters = useUserFilters();
const tableFilters: TableFilter[] = useMemo(() => {
return [
{
name: 'status',
label: t`Status`,
description: t`Filter by order status`,
choiceFunction: StatusFilterOptions(ModelType.purchaseorder)
},
OrderStatusFilter({ model: ModelType.purchaseorder }),
OutstandingFilter(),
OverdueFilter(),
AssignedToMeFilter(),
@@ -77,21 +81,16 @@ export function PurchaseOrderTable({
TargetDateAfterFilter(),
CompletedBeforeFilter(),
CompletedAfterFilter(),
{
name: 'project_code',
label: t`Project Code`,
description: t`Filter by project code`,
choices: projectCodeFilters.choices
},
ProjectCodeFilter({ choices: projectCodeFilters.choices }),
HasProjectCodeFilter(),
{
name: 'assigned_to',
label: t`Responsible`,
description: t`Filter by responsible owner`,
choices: responsibleFilters.choices
}
ResponsibleFilter({ choices: responsibleFilters.choices }),
CreatedByFilter({ choices: createdByFilters.choices })
];
}, [projectCodeFilters.choices, responsibleFilters.choices]);
}, [
projectCodeFilters.choices,
responsibleFilters.choices,
createdByFilters.choices
]);
const tableColumns = useMemo(() => {
return [
@@ -120,6 +119,7 @@ export function PurchaseOrderTable({
StatusColumn({ model: ModelType.purchaseorder }),
ProjectCodeColumn({}),
CreationDateColumn({}),
CreatedByColumn({}),
TargetDateColumn({}),
CompletionDateColumn({
accessor: 'complete_date'

View File

@@ -8,13 +8,18 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { useReturnOrderFields } from '../../forms/ReturnOrderForms';
import { useOwnerFilters, useProjectCodeFilters } from '../../hooks/UseFilter';
import {
useOwnerFilters,
useProjectCodeFilters,
useUserFilters
} from '../../hooks/UseFilter';
import { useCreateApiFormModal } from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable';
import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState';
import {
CompletionDateColumn,
CreatedByColumn,
CreationDateColumn,
DescriptionColumn,
LineItemsProgressColumn,
@@ -30,12 +35,15 @@ import {
CompletedBeforeFilter,
CreatedAfterFilter,
CreatedBeforeFilter,
CreatedByFilter,
HasProjectCodeFilter,
MaxDateFilter,
MinDateFilter,
OrderStatusFilter,
OutstandingFilter,
OverdueFilter,
StatusFilterOptions,
ProjectCodeFilter,
ResponsibleFilter,
type TableFilter,
TargetDateAfterFilter,
TargetDateBeforeFilter
@@ -54,15 +62,11 @@ export function ReturnOrderTable({
const projectCodeFilters = useProjectCodeFilters();
const responsibleFilters = useOwnerFilters();
const createdByFilters = useUserFilters();
const tableFilters: TableFilter[] = useMemo(() => {
const filters: TableFilter[] = [
{
name: 'status',
label: t`Status`,
description: t`Filter by order status`,
choiceFunction: StatusFilterOptions(ModelType.returnorder)
},
OrderStatusFilter({ model: ModelType.returnorder }),
OutstandingFilter(),
OverdueFilter(),
AssignedToMeFilter(),
@@ -74,19 +78,10 @@ export function ReturnOrderTable({
TargetDateAfterFilter(),
CompletedBeforeFilter(),
CompletedAfterFilter(),
{
name: 'project_code',
label: t`Project Code`,
description: t`Filter by project code`,
choices: projectCodeFilters.choices
},
HasProjectCodeFilter(),
{
name: 'assigned_to',
label: t`Responsible`,
description: t`Filter by responsible owner`,
choices: responsibleFilters.choices
}
ProjectCodeFilter({ choices: projectCodeFilters.choices }),
ResponsibleFilter({ choices: responsibleFilters.choices }),
CreatedByFilter({ choices: createdByFilters.choices })
];
if (!!partId) {
@@ -99,7 +94,12 @@ export function ReturnOrderTable({
}
return filters;
}, [partId, projectCodeFilters.choices, responsibleFilters.choices]);
}, [
partId,
projectCodeFilters.choices,
responsibleFilters.choices,
createdByFilters.choices
]);
const tableColumns = useMemo(() => {
return [
@@ -128,6 +128,7 @@ export function ReturnOrderTable({
StatusColumn({ model: ModelType.returnorder }),
ProjectCodeColumn({}),
CreationDateColumn({}),
CreatedByColumn({}),
TargetDateColumn({}),
CompletionDateColumn({
accessor: 'complete_date'

View File

@@ -9,12 +9,17 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { useSalesOrderFields } from '../../forms/SalesOrderForms';
import { useOwnerFilters, useProjectCodeFilters } from '../../hooks/UseFilter';
import {
useOwnerFilters,
useProjectCodeFilters,
useUserFilters
} from '../../hooks/UseFilter';
import { useCreateApiFormModal } from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable';
import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState';
import {
CreatedByColumn,
CreationDateColumn,
DescriptionColumn,
LineItemsProgressColumn,
@@ -31,12 +36,15 @@ import {
CompletedBeforeFilter,
CreatedAfterFilter,
CreatedBeforeFilter,
CreatedByFilter,
HasProjectCodeFilter,
MaxDateFilter,
MinDateFilter,
OrderStatusFilter,
OutstandingFilter,
OverdueFilter,
StatusFilterOptions,
ProjectCodeFilter,
ResponsibleFilter,
type TableFilter,
TargetDateAfterFilter,
TargetDateBeforeFilter
@@ -55,15 +63,11 @@ export function SalesOrderTable({
const projectCodeFilters = useProjectCodeFilters();
const responsibleFilters = useOwnerFilters();
const createdByFilters = useUserFilters();
const tableFilters: TableFilter[] = useMemo(() => {
const filters: TableFilter[] = [
{
name: 'status',
label: t`Status`,
description: t`Filter by order status`,
choiceFunction: StatusFilterOptions(ModelType.salesorder)
},
OrderStatusFilter({ model: ModelType.salesorder }),
OutstandingFilter(),
OverdueFilter(),
AssignedToMeFilter(),
@@ -75,19 +79,10 @@ export function SalesOrderTable({
TargetDateAfterFilter(),
CompletedBeforeFilter(),
CompletedAfterFilter(),
{
name: 'project_code',
label: t`Project Code`,
description: t`Filter by project code`,
choices: projectCodeFilters.choices
},
HasProjectCodeFilter(),
{
name: 'assigned_to',
label: t`Responsible`,
description: t`Filter by responsible owner`,
choices: responsibleFilters.choices
}
ProjectCodeFilter({ choices: projectCodeFilters.choices }),
ResponsibleFilter({ choices: responsibleFilters.choices }),
CreatedByFilter({ choices: createdByFilters.choices })
];
if (!!partId) {
@@ -100,7 +95,12 @@ export function SalesOrderTable({
}
return filters;
}, [partId, projectCodeFilters.choices, responsibleFilters.choices]);
}, [
partId,
projectCodeFilters.choices,
responsibleFilters.choices,
createdByFilters.choices
]);
const salesOrderFields = useSalesOrderFields({});
@@ -165,6 +165,7 @@ export function SalesOrderTable({
StatusColumn({ model: ModelType.salesorder }),
ProjectCodeColumn({}),
CreationDateColumn({}),
CreatedByColumn({}),
TargetDateColumn({}),
ShipmentDateColumn({}),
ResponsibleColumn({}),

View File

@@ -10,12 +10,12 @@ import { useQuery } from '@tanstack/react-query';
import { DataTable } from 'mantine-datatable';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { api } from '../../App';
import { AddItemButton } from '../../components/buttons/AddItemButton';
import { PassFailButton } from '../../components/buttons/YesNoButton';
import type { ApiFormFieldSet } from '../../components/forms/fields/ApiFormField';
import { AttachmentLink } from '../../components/items/AttachmentLink';
import { RenderUser } from '../../components/render/User';
import { useApi } from '../../contexts/ApiContext';
import { formatDate } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { UserRoles } from '../../enums/Roles';
@@ -47,6 +47,7 @@ export default function StockItemTestResultTable({
partId: number;
itemId: number;
}>) {
const api = useApi();
const user = useUserState();
const table = useTable('stocktests');

View File

@@ -1,9 +1,9 @@
import { QueryClientProvider } from '@tanstack/react-query';
import { useEffect } from 'react';
import { BrowserRouter } from 'react-router-dom';
import { queryClient } from '../App';
import { BaseContext } from '../contexts/BaseContext';
import { api, queryClient } from '../App';
import { ApiProvider } from '../contexts/ApiContext';
import { ThemeContext } from '../contexts/ThemeContext';
import { defaultHostList } from '../defaults/defaultHostList';
import { base_url } from '../main';
import { routes } from '../router';
@@ -19,10 +19,10 @@ export default function DesktopAppView() {
}, [hostList]);
return (
<BaseContext>
<QueryClientProvider client={queryClient}>
<ApiProvider client={queryClient} api={api}>
<ThemeContext>
<BrowserRouter basename={base_url}>{routes}</BrowserRouter>
</QueryClientProvider>
</BaseContext>
</ThemeContext>
</ApiProvider>
);
}

View File

@@ -1,7 +1,7 @@
import { Trans } from '@lingui/macro';
import { Anchor, Center, Container, Stack, Text, Title } from '@mantine/core';
import { BaseContext } from '../contexts/BaseContext';
import { ThemeContext } from '../contexts/ThemeContext';
import { docLinks } from '../defaults/links';
import { IS_DEV } from '../main';
import { useLocalState } from '../states/LocalState';
@@ -14,7 +14,7 @@ export default function MobileAppView() {
window.location.reload();
}
return (
<BaseContext>
<ThemeContext>
<Center h='100vh'>
<Container>
<Stack>
@@ -38,6 +38,6 @@ export default function MobileAppView() {
</Stack>
</Container>
</Center>
</BaseContext>
</ThemeContext>
);
}

View File

@@ -34,6 +34,7 @@ export const doQuickLogin = async (
await page.goto(`${url}/login/?login=${username}&password=${password}`);
await page.waitForURL('**/platform/home');
await page.getByLabel('navigation-menu').waitFor();
await page.getByText(/InvenTree Demo Server -/).waitFor();
};

View File

@@ -35,8 +35,24 @@ test('Purchase Orders - Barcodes', async ({ page }) => {
await page.getByRole('img', { name: 'QR Code' }).waitFor();
await page.getByRole('banner').getByRole('button').click();
// Link to barcode
// Un-link barcode if a link exists
await page.getByLabel('action-menu-barcode-actions').click();
await page.waitForTimeout(100);
if (
await page
.getByLabel('action-menu-barcode-actions-unlink-barcode')
.isVisible()
) {
await page.getByLabel('action-menu-barcode-actions-unlink-barcode').click();
await page.getByRole('button', { name: 'Unlink Barcode' }).click();
await page.waitForTimeout(100);
} else {
await page.keyboard.press('Escape');
}
// Link to barcode
await page.getByLabel('action-menu-barcode-actions', { exact: true }).click();
await page.getByLabel('action-menu-barcode-actions-link-barcode').click();
await page.getByLabel('barcode-input-scanner').click();
@@ -49,7 +65,7 @@ test('Purchase Orders - Barcodes', async ({ page }) => {
await page.getByRole('button', { name: 'Issue Order' }).waitFor();
// Ensure we can scan back to this page, with the associated barcode
await page.goto(`${baseUrl}/home`);
await page.goto(`${baseUrl}/`);
await page.waitForTimeout(250);
await page.getByRole('button', { name: 'Open Barcode Scanner' }).click();
await page.getByPlaceholder('Enter barcode data').fill('1234567890');