mirror of
https://github.com/inventree/InvenTree.git
synced 2026-09-04 11:58:42 +00:00
Merge commit '956468eb847754e701d207bae2a51a6a00ca7d56' into block-notes
This commit is contained in:
@@ -10,6 +10,7 @@ export interface ModelInformationInterface {
|
||||
url_detail?: string;
|
||||
api_endpoint: ApiEndpoints;
|
||||
admin_url?: string;
|
||||
pk_field?: string;
|
||||
supports_barcode?: boolean;
|
||||
icon: keyof InvenTreeIconType;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface InstanceRenderInterface {
|
||||
link?: boolean;
|
||||
navigate?: any;
|
||||
showSecondary?: boolean;
|
||||
showHover?: boolean;
|
||||
extra?: Record<string, any>;
|
||||
}
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
"@lingui/babel-plugin-lingui-macro": "^5.9.2",
|
||||
"@lingui/cli": "^5.9.2",
|
||||
"@lingui/macro": "^5.9.2",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@playwright/test": "^1.16.0",
|
||||
"@types/node": "^25.5.0",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react": "^19.2.14",
|
||||
|
||||
@@ -271,7 +271,7 @@ function NavTabs() {
|
||||
|
||||
// static content
|
||||
mainNavTabs.forEach((tab) => {
|
||||
if (tab.role && !user.hasViewRole(tab.role)) {
|
||||
if (tab.visible === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
ActionIcon,
|
||||
Alert,
|
||||
Anchor,
|
||||
Box,
|
||||
Group,
|
||||
HoverCard,
|
||||
type MantineSize,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Space,
|
||||
Stack,
|
||||
Text
|
||||
} from '@mantine/core';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { type ReactNode, useCallback } from 'react';
|
||||
import { type ReactNode, useCallback, useMemo } from 'react';
|
||||
|
||||
import { ModelInformationDict } from '@lib/enums/ModelInformation';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
@@ -25,8 +29,11 @@ import type {
|
||||
|
||||
export type { InstanceRenderInterface } from '@lib/types/Rendering';
|
||||
import { getBaseUrl, navigateToLink, shortenString } from '@lib/index';
|
||||
import { IconLink } from '@tabler/icons-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useApi } from '../../contexts/ApiContext';
|
||||
import { usePluginState } from '../../states/PluginState';
|
||||
import { useUserSettingsState } from '../../states/SettingsStates';
|
||||
import { Thumbnail } from '../images/Thumbnail';
|
||||
import { RenderBuildItem, RenderBuildLine, RenderBuildOrder } from './Build';
|
||||
import {
|
||||
@@ -125,11 +132,95 @@ export function RenderInstance(props: RenderInstanceProps): ReactNode {
|
||||
props.custom_model ?? props.model ?? ''
|
||||
);
|
||||
|
||||
// provider component
|
||||
if (!RenderComponent) {
|
||||
return <UnknownRenderer model={props.model} />;
|
||||
}
|
||||
return <RenderComponent {...props} />;
|
||||
const navigate = useNavigate();
|
||||
const userSettings = useUserSettingsState();
|
||||
|
||||
// Extract model information from the defined model type
|
||||
const modelInfo = useMemo(() => {
|
||||
if (!props.model) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return ModelInformationDict[
|
||||
props.model.toString().toLowerCase() as ModelType
|
||||
];
|
||||
}, [props.model]);
|
||||
|
||||
const showHover: boolean = useMemo(() => {
|
||||
if (!modelInfo) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Override with the props.showHover attribute
|
||||
if (props.showHover !== undefined) {
|
||||
return props.showHover;
|
||||
}
|
||||
|
||||
// If not specified, fall back to the user configured setting
|
||||
return userSettings.isSet('SHOW_EXTRA_MODEL_INFO');
|
||||
}, [props.showHover, modelInfo, userSettings]);
|
||||
|
||||
// Extract model ID from the provided instance data, using the defined primary key field (or 'pk' as a fallback)
|
||||
const modelId = useMemo(() => {
|
||||
if (!modelInfo || !props.instance) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return props.instance[modelInfo.pk_field ?? 'pk'];
|
||||
}, [modelInfo, props.instance]);
|
||||
|
||||
const detailUrl = useMemo(() => {
|
||||
if (!modelInfo || !modelId || !modelInfo.url_detail) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return modelInfo.url_detail.replace(':pk', modelId.toString());
|
||||
}, [modelInfo, modelId]);
|
||||
|
||||
return (
|
||||
<HoverCard
|
||||
disabled={!showHover}
|
||||
position='top-end'
|
||||
withinPortal
|
||||
openDelay={500}
|
||||
closeDelay={100}
|
||||
zIndex={99999}
|
||||
>
|
||||
<HoverCard.Target>
|
||||
<Box>
|
||||
{!!RenderComponent ? (
|
||||
<RenderComponent {...props} />
|
||||
) : (
|
||||
<UnknownRenderer model={props.model} />
|
||||
)}
|
||||
</Box>
|
||||
</HoverCard.Target>
|
||||
<HoverCard.Dropdown>
|
||||
<Stack gap='xs'>
|
||||
<Group justify='space-between'>
|
||||
<Text size='sm' fw='bold'>
|
||||
{modelInfo?.label()}
|
||||
</Text>
|
||||
{modelId && <Text size='xs'>{`[${t`ID`}: ${modelId}]`}</Text>}
|
||||
</Group>
|
||||
{detailUrl && (
|
||||
<Anchor
|
||||
href={detailUrl}
|
||||
target='_blank'
|
||||
onClick={(event) => navigateToLink(detailUrl, navigate, event)}
|
||||
>
|
||||
<Group gap='xs' wrap='nowrap'>
|
||||
<ActionIcon variant='transparent' size='xs'>
|
||||
<IconLink />
|
||||
</ActionIcon>
|
||||
<Text size='sm'>{t`View details`}</Text>
|
||||
</Group>
|
||||
</Anchor>
|
||||
)}
|
||||
</Stack>
|
||||
</HoverCard.Dropdown>
|
||||
</HoverCard>
|
||||
);
|
||||
}
|
||||
|
||||
export function RenderRemoteInstance({
|
||||
|
||||
@@ -124,6 +124,17 @@ export function getActions(navigate: NavigateFunction) {
|
||||
leftSection: <IconLink size='1.2rem' />
|
||||
});
|
||||
|
||||
globalSettings.isSet('TRANSFERORDER_ENABLED') &&
|
||||
user?.hasViewRole(UserRoles.transfer_order) &&
|
||||
_actions.push({
|
||||
id: 'transfer-orders',
|
||||
label: t`Transfer Orders`,
|
||||
description: t`Go to Transfer Orders`,
|
||||
onClick: () =>
|
||||
navigate(ModelInformationDict['transferorder'].url_overview!),
|
||||
leftSection: <IconLink size='1.2rem' />
|
||||
});
|
||||
|
||||
globalSettings.isSet('RETURNORDER_ENABLED') &&
|
||||
user?.hasViewRole(UserRoles.return_order) &&
|
||||
_actions.push({
|
||||
@@ -172,6 +183,17 @@ export function getActions(navigate: NavigateFunction) {
|
||||
});
|
||||
|
||||
staff &&
|
||||
user?.hasViewPermission(ModelType.error) &&
|
||||
_actions.push({
|
||||
id: 'error-logs',
|
||||
label: t`Error Logs`,
|
||||
description: t`View error logs for this instance`,
|
||||
onClick: () => navigate('/settings/admin/errors'),
|
||||
leftSection: <IconReport size='1.2rem' />
|
||||
});
|
||||
|
||||
staff &&
|
||||
user?.hasViewPermission(ModelType.pluginconfig) &&
|
||||
_actions.push({
|
||||
id: 'plugin-settings',
|
||||
label: t`Plugins`,
|
||||
|
||||
@@ -16,15 +16,18 @@ import {
|
||||
} from '@tabler/icons-react';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { MenuLinkItem } from '../components/items/MenuLinks';
|
||||
import { useGlobalSettingsState } from '../states/SettingsStates';
|
||||
|
||||
type NavTab = {
|
||||
name: string;
|
||||
title: string;
|
||||
icon: ReactNode;
|
||||
role?: UserRoles;
|
||||
visible?: boolean;
|
||||
};
|
||||
|
||||
export function getNavTabs(user: UserStateProps): NavTab[] {
|
||||
const globalSettings = useGlobalSettingsState.getState();
|
||||
|
||||
const navTabs: NavTab[] = [
|
||||
{
|
||||
name: 'home',
|
||||
@@ -35,37 +38,45 @@ export function getNavTabs(user: UserStateProps): NavTab[] {
|
||||
name: 'part',
|
||||
title: t`Parts`,
|
||||
icon: <IconBox />,
|
||||
role: UserRoles.part
|
||||
visible:
|
||||
user.hasViewRole(UserRoles.part) ||
|
||||
user.hasViewRole(UserRoles.part_category)
|
||||
},
|
||||
{
|
||||
name: 'stock',
|
||||
title: t`Stock`,
|
||||
icon: <IconPackages />,
|
||||
role: UserRoles.stock
|
||||
visible:
|
||||
user.hasViewRole(UserRoles.stock) ||
|
||||
user.hasViewRole(UserRoles.stock_location) ||
|
||||
(globalSettings.isSet('TRANSFERORDER_ENABLED') &&
|
||||
user.hasViewRole(UserRoles.transfer_order))
|
||||
},
|
||||
{
|
||||
name: 'manufacturing',
|
||||
title: t`Manufacturing`,
|
||||
icon: <IconBuildingFactory2 />,
|
||||
role: UserRoles.build
|
||||
visible: user.hasViewRole(UserRoles.build)
|
||||
},
|
||||
{
|
||||
name: 'purchasing',
|
||||
title: t`Purchasing`,
|
||||
icon: <IconShoppingCart />,
|
||||
role: UserRoles.purchase_order
|
||||
visible: user.hasViewRole(UserRoles.purchase_order)
|
||||
},
|
||||
{
|
||||
name: 'sales',
|
||||
title: t`Sales`,
|
||||
icon: <IconTruckDelivery />,
|
||||
role: UserRoles.sales_order
|
||||
visible:
|
||||
user.hasViewRole(UserRoles.sales_order) ||
|
||||
(globalSettings.isSet('RETURNORDER_ENABLED') &&
|
||||
user.hasViewRole(UserRoles.return_order))
|
||||
}
|
||||
];
|
||||
|
||||
return navTabs.filter((tab) => {
|
||||
if (!tab.role) return true;
|
||||
return user.hasViewRole(tab.role);
|
||||
return tab.visible !== false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ApiFormFieldSet } from '@lib/types/Forms';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { IconBuildingStore, IconCopy, IconPackages } from '@tabler/icons-react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useGlobalSettingsState } from '../states/SettingsStates';
|
||||
|
||||
/**
|
||||
@@ -23,6 +23,12 @@ export function usePartFields({
|
||||
undefined
|
||||
);
|
||||
|
||||
// Set the initial state for the tracked fields based on the global settings
|
||||
useEffect(() => {
|
||||
setVirtual(globalSettings.isSet('PART_VIRTUAL'));
|
||||
setPurchaseable(globalSettings.isSet('PART_PURCHASEABLE'));
|
||||
}, [partId, create]);
|
||||
|
||||
return useMemo(() => {
|
||||
const fields: ApiFormFieldSet = {
|
||||
category: {
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
import { ActionButton } from '@lib/components/ActionButton';
|
||||
import { StylishText } from '@lib/components/StylishText';
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import type {
|
||||
ApiFormAdjustFilterType,
|
||||
ApiFormFieldChoice,
|
||||
ApiFormFieldSet,
|
||||
ApiFormModalProps,
|
||||
StockOperationProps
|
||||
} from '@lib/types/Forms';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
Alert,
|
||||
@@ -20,27 +33,13 @@ import {
|
||||
IconUsersGroup
|
||||
} from '@tabler/icons-react';
|
||||
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
||||
import { type JSX, Suspense, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { ActionButton } from '@lib/components/ActionButton';
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import dayjs from 'dayjs';
|
||||
import { type JSX, Suspense, useEffect, useMemo, useState } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { api } from '../App';
|
||||
import RemoveRowButton from '../components/buttons/RemoveRowButton';
|
||||
import { StandaloneField } from '../components/forms/StandaloneField';
|
||||
|
||||
import { StylishText } from '@lib/components/StylishText';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import type {
|
||||
ApiFormAdjustFilterType,
|
||||
ApiFormFieldChoice,
|
||||
ApiFormFieldSet,
|
||||
ApiFormModalProps,
|
||||
StockOperationProps
|
||||
} from '@lib/types/Forms';
|
||||
import {
|
||||
TableFieldExtraRow,
|
||||
type TableFieldRowProps
|
||||
@@ -490,12 +489,31 @@ function StockItemDefaultMove({
|
||||
function moveToDefault(
|
||||
stockItem: any,
|
||||
value: StockItemQuantity,
|
||||
refresh: () => void
|
||||
refresh: () => void,
|
||||
options?: {
|
||||
title?: string;
|
||||
onConfirm?: (location: number) => void;
|
||||
}
|
||||
) {
|
||||
const location =
|
||||
stockItem.part_detail?.default_location ??
|
||||
stockItem.part_detail?.category_default_location;
|
||||
|
||||
modals.openConfirmModal({
|
||||
title: <StylishText>{t`Confirm Stock Transfer`}</StylishText>,
|
||||
title: (
|
||||
<StylishText>{options?.title ?? t`Confirm Stock Transfer`}</StylishText>
|
||||
),
|
||||
children: <StockItemDefaultMove stockItem={stockItem} value={value} />,
|
||||
onConfirm: () => {
|
||||
if (!location) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (options?.onConfirm) {
|
||||
options.onConfirm(location);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
stockItem.location === stockItem.part_detail?.default_location ||
|
||||
stockItem.location === stockItem.part_detail?.category_default_location
|
||||
@@ -512,9 +530,7 @@ function moveToDefault(
|
||||
status: stockItem.status
|
||||
}
|
||||
],
|
||||
location:
|
||||
stockItem.part_detail?.default_location ??
|
||||
stockItem.part_detail?.category_default_location
|
||||
location: location
|
||||
})
|
||||
.then((response) => {
|
||||
refresh();
|
||||
@@ -548,6 +564,7 @@ function StockOperationsRow({
|
||||
add = false,
|
||||
setMax = false,
|
||||
merge = false,
|
||||
returnStock = false,
|
||||
record
|
||||
}: {
|
||||
props: TableFieldRowProps;
|
||||
@@ -556,8 +573,11 @@ function StockOperationsRow({
|
||||
add?: boolean;
|
||||
setMax?: boolean;
|
||||
merge?: boolean;
|
||||
returnStock?: boolean;
|
||||
record?: any;
|
||||
}) {
|
||||
const form = useFormContext();
|
||||
|
||||
const statusOptions: ApiFormFieldChoice[] = useMemo(() => {
|
||||
return (
|
||||
StatusFilterOptions(ModelType.stockitem)()?.map((choice) => {
|
||||
@@ -676,7 +696,22 @@ function StockOperationsRow({
|
||||
{transfer && (
|
||||
<ActionButton
|
||||
onClick={() =>
|
||||
moveToDefault(record, props.item.quantity, removeAndRefresh)
|
||||
moveToDefault(
|
||||
record,
|
||||
props.item.quantity,
|
||||
removeAndRefresh,
|
||||
returnStock
|
||||
? {
|
||||
title: t`Confirm Stock Return`,
|
||||
onConfirm: (location: number) => {
|
||||
form.setValue('location', location, {
|
||||
shouldDirty: true,
|
||||
shouldValidate: true
|
||||
});
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
)
|
||||
}
|
||||
icon={<InvenTreeIcon icon='default_location' />}
|
||||
tooltip={t`Move to default location`}
|
||||
@@ -840,6 +875,7 @@ function stockReturnFields(items: any[]): ApiFormFieldSet {
|
||||
key={record.pk}
|
||||
record={record}
|
||||
transfer
|
||||
returnStock
|
||||
changeStatus
|
||||
/>
|
||||
);
|
||||
@@ -854,12 +890,25 @@ function stockReturnFields(items: any[]): ApiFormFieldSet {
|
||||
]
|
||||
},
|
||||
location: {
|
||||
field_type: 'related field',
|
||||
api_url: apiUrl(ApiEndpoints.stock_location_list),
|
||||
model: ModelType.stocklocation,
|
||||
required: true,
|
||||
filters: {
|
||||
structural: false
|
||||
}
|
||||
},
|
||||
merge: {},
|
||||
notes: {}
|
||||
merge: {
|
||||
field_type: 'boolean',
|
||||
label: t`Merge into existing stock`,
|
||||
description: t`Merge returned items into existing stock items if possible`,
|
||||
value: false
|
||||
},
|
||||
notes: {
|
||||
field_type: 'string',
|
||||
label: t`Notes`,
|
||||
description: t`Stock transaction notes`
|
||||
}
|
||||
};
|
||||
|
||||
return fields;
|
||||
@@ -967,6 +1016,9 @@ function stockCountFields(items: any[]): ApiFormFieldSet {
|
||||
|
||||
const initialValue = mapAdjustmentItems(items);
|
||||
|
||||
// Extract all location values from the items
|
||||
const locations = [...new Set(items.map((item) => item.location))];
|
||||
|
||||
const fields: ApiFormFieldSet = {
|
||||
items: {
|
||||
field_type: 'table',
|
||||
@@ -990,6 +1042,12 @@ function stockCountFields(items: any[]): ApiFormFieldSet {
|
||||
{ title: t`Actions` }
|
||||
]
|
||||
},
|
||||
location: {
|
||||
value: locations.length === 1 ? locations[0] : undefined,
|
||||
filters: {
|
||||
structural: false
|
||||
}
|
||||
},
|
||||
notes: {}
|
||||
};
|
||||
|
||||
@@ -1584,11 +1642,7 @@ export function useTestResultFields({
|
||||
/**
|
||||
* Modal form for finding a particular stock item by serial number
|
||||
*/
|
||||
export function useFindSerialNumberForm({
|
||||
partId
|
||||
}: {
|
||||
partId: number;
|
||||
}) {
|
||||
export function useFindSerialNumberForm({ partId }: { partId: number }) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return useApiFormModal({
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -61,6 +61,7 @@ export default function UserSettings() {
|
||||
'FORMS_CLOSE_USING_ESCAPE',
|
||||
'DISPLAY_STOCKTAKE_TAB',
|
||||
'ENABLE_LAST_BREADCRUMB',
|
||||
'SHOW_EXTRA_MODEL_INFO',
|
||||
'SHOW_FULL_LOCATION_IN_TABLES',
|
||||
'SHOW_FULL_CATEGORY_IN_TABLES',
|
||||
'SHOW_BOM_SUBASSEMBLY_LEVELS'
|
||||
|
||||
@@ -331,7 +331,7 @@ export default function BuildDetail() {
|
||||
name: 'can_build',
|
||||
unit: build.part_detail?.units,
|
||||
label: t`Can Build`,
|
||||
hidden: partRequirementsQuery.isFetching
|
||||
hidden: partRequirements?.can_build === undefined
|
||||
},
|
||||
{
|
||||
type: 'progressbar',
|
||||
@@ -454,12 +454,7 @@ export default function BuildDetail() {
|
||||
<DetailsTable fields={br} item={data} />
|
||||
</ItemDetailsGrid>
|
||||
);
|
||||
}, [
|
||||
build,
|
||||
instanceQuery,
|
||||
partRequirements,
|
||||
partRequirementsQuery.isFetching
|
||||
]);
|
||||
}, [build, instanceQuery, partRequirements, partRequirementsQuery]);
|
||||
|
||||
const buildPanels: PanelType[] = useMemo(() => {
|
||||
return [
|
||||
@@ -597,6 +592,7 @@ export default function BuildDetail() {
|
||||
build,
|
||||
id,
|
||||
user,
|
||||
partRequirements,
|
||||
buildStatus,
|
||||
globalSettings,
|
||||
showChildBuilds,
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
Stack,
|
||||
Text
|
||||
} from '@mantine/core';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import {
|
||||
IconBookmarks,
|
||||
IconBuilding,
|
||||
@@ -1165,11 +1166,25 @@ export default function PartDetail() {
|
||||
variant='transparent'
|
||||
disabled={!user.hasChangeRole(UserRoles.part)}
|
||||
onClick={() => {
|
||||
const locking = !part.locked;
|
||||
api
|
||||
.patch(apiUrl(ApiEndpoints.part_list, part.pk), {
|
||||
locked: !part.locked
|
||||
locked: locking
|
||||
})
|
||||
.then(refreshInstance);
|
||||
.then(() => {
|
||||
notifications.hide('part-lock');
|
||||
notifications.show({
|
||||
id: 'part-lock',
|
||||
message: locking ? t`Part locked` : t`Part unlocked`,
|
||||
color: 'green',
|
||||
icon: locking ? (
|
||||
<IconLock size='1rem' />
|
||||
) : (
|
||||
<IconLockOpen size='1rem' />
|
||||
)
|
||||
});
|
||||
refreshInstance();
|
||||
});
|
||||
}}
|
||||
>
|
||||
{part?.locked ? <IconLock /> : <IconLockOpen />}
|
||||
|
||||
@@ -261,7 +261,7 @@ export default function PluginListTable() {
|
||||
const [pluginPackage, setPluginPackage] = useState<string>('');
|
||||
|
||||
const activatePluginModal = useEditApiFormModal({
|
||||
title: t`Activate Plugin`,
|
||||
title: activate ? t`Activate Plugin` : t`Deactivate Plugin`,
|
||||
url: ApiEndpoints.plugin_activate,
|
||||
pathParams: { key: selectedPluginKey },
|
||||
preFormContent: activateModalContent,
|
||||
|
||||
@@ -45,8 +45,12 @@ test('Build Order - Basic Tests', async ({ browser }) => {
|
||||
// Load a particular build order
|
||||
await page.getByRole('cell', { name: 'BO0017' }).click();
|
||||
|
||||
await loadTab(page, 'Build Details');
|
||||
|
||||
// This build order should be "on hold"
|
||||
await page.getByText('On Hold').first().waitFor();
|
||||
await page.getByText('Can Build').first().waitFor();
|
||||
await page.getByText('Completed Outputs').first().waitFor();
|
||||
|
||||
// Edit the build order (via keyboard shortcut)
|
||||
await page.keyboard.press('Control+E');
|
||||
|
||||
@@ -290,8 +290,6 @@ test('Parts - BOM Validation', async ({ browser }) => {
|
||||
// Edit line item, to ensure BOM is not valid
|
||||
const cell = await page.getByRole('cell', { name: 'paint', exact: true });
|
||||
|
||||
// await cell.click({ button: 'right' });
|
||||
// await page.getByRole('button', { name: 'Edit', exact: true }).click();
|
||||
await clickOnRowMenu(cell);
|
||||
await page.getByRole('menuitem', { name: 'Edit', exact: true }).click();
|
||||
|
||||
|
||||
+434
-430
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user