diff --git a/InvenTree/company/serializers.py b/InvenTree/company/serializers.py
index a91176d862..860818f8dd 100644
--- a/InvenTree/company/serializers.py
+++ b/InvenTree/company/serializers.py
@@ -372,6 +372,7 @@ class SupplierPartSerializer(InvenTreeTagModelSerializer):
# Annotated field showing total in-stock quantity
in_stock = serializers.FloatField(read_only=True)
+ available = serializers.FloatField(required=False)
part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
diff --git a/src/frontend/src/components/forms/ApiForm.tsx b/src/frontend/src/components/forms/ApiForm.tsx
index 3370f368eb..3c7e526694 100644
--- a/src/frontend/src/components/forms/ApiForm.tsx
+++ b/src/frontend/src/components/forms/ApiForm.tsx
@@ -16,7 +16,6 @@ import { ApiFormField, ApiFormFieldSet } from './fields/ApiFormField';
/**
* Properties for the ApiForm component
- * @param name : The name (identifier) for this form
* @param url : The API endpoint to fetch the form data from
* @param pk : Optional primary-key value when editing an existing object
* @param title : The title to display in the form header
@@ -35,7 +34,6 @@ import { ApiFormField, ApiFormFieldSet } from './fields/ApiFormField';
* @param onFormError : A callback function to call when the form is submitted with errors.
*/
export interface ApiFormProps {
- name: string;
url: ApiPaths;
pk?: number | string | undefined;
title: string;
@@ -104,7 +102,7 @@ export function ApiForm({
// Query manager for retrieiving initial data from the server
const initialDataQuery = useQuery({
enabled: false,
- queryKey: ['form-initial-data', props.name, props.url, props.pk],
+ queryKey: ['form-initial-data', modalId, props.method, props.url, props.pk],
queryFn: async () => {
return api
.get(url)
@@ -150,7 +148,13 @@ export function ApiForm({
// Fetch initial data if the fetchInitialData property is set
if (props.fetchInitialData) {
queryClient.removeQueries({
- queryKey: ['form-initial-data', props.name, props.url, props.pk]
+ queryKey: [
+ 'form-initial-data',
+ modalId,
+ props.method,
+ props.url,
+ props.pk
+ ]
});
initialDataQuery.refetch();
}
@@ -159,7 +163,7 @@ export function ApiForm({
// Query manager for submitting data
const submitQuery = useQuery({
enabled: false,
- queryKey: ['form-submit', props.name, props.url, props.pk],
+ queryKey: ['form-submit', modalId, props.method, props.url, props.pk],
queryFn: async () => {
let method = props.method?.toLowerCase() ?? 'get';
diff --git a/src/frontend/src/components/images/Thumbnail.tsx b/src/frontend/src/components/images/Thumbnail.tsx
index 43f0effb72..80a4f7a9f7 100644
--- a/src/frontend/src/components/images/Thumbnail.tsx
+++ b/src/frontend/src/components/images/Thumbnail.tsx
@@ -2,35 +2,43 @@ import { t } from '@lingui/macro';
import { Anchor } from '@mantine/core';
import { Group } from '@mantine/core';
import { Text } from '@mantine/core';
-import { useMemo } from 'react';
+import { ReactNode, useMemo } from 'react';
import { ApiImage } from './ApiImage';
+/*
+ * Render an image, loaded via the API
+ */
export function Thumbnail({
src,
alt = t`Thumbnail`,
- size = 20
+ size = 20,
+ text
}: {
src?: string | undefined;
alt?: string;
size?: number;
+ text?: ReactNode;
}) {
- // TODO: Use HoverCard to display a larger version of the image
+ const backup_image = '/static/img/blank_image.png';
return (
-
+
+
+ {text}
+
);
}
@@ -39,7 +47,7 @@ export function ThumbnailHoverCard({
text,
link = '',
alt = t`Thumbnail`,
- size = 24
+ size = 20
}: {
src: string;
text: string;
@@ -56,12 +64,13 @@ export function ThumbnailHoverCard({
);
}, [src, text, alt, size]);
- if (link)
+ if (link) {
return (
{card}
);
+ }
return
{card}
;
}
diff --git a/src/frontend/src/components/render/Company.tsx b/src/frontend/src/components/render/Company.tsx
index 06485839f2..a1d2b40e87 100644
--- a/src/frontend/src/components/render/Company.tsx
+++ b/src/frontend/src/components/render/Company.tsx
@@ -70,15 +70,20 @@ export function RenderSupplierPart({ instance }: { instance: any }): ReactNode {
/**
* Inline rendering of a single ManufacturerPart instance
*/
-export function ManufacturerPart({ instance }: { instance: any }): ReactNode {
- let supplier = instance.supplier_detail ?? {};
+export function RenderManufacturerPart({
+ instance
+}: {
+ instance: any;
+}): ReactNode {
let part = instance.part_detail ?? {};
+ let manufacturer = instance.manufacturer_detail ?? {};
- let text = instance.SKU;
-
- if (supplier.name) {
- text = `${supplier.name} | ${text}`;
- }
-
- return ;
+ return (
+
+ );
}
diff --git a/src/frontend/src/components/render/Instance.tsx b/src/frontend/src/components/render/Instance.tsx
index 277a901364..c87e61c394 100644
--- a/src/frontend/src/components/render/Instance.tsx
+++ b/src/frontend/src/components/render/Instance.tsx
@@ -9,6 +9,7 @@ import {
RenderAddress,
RenderCompany,
RenderContact,
+ RenderManufacturerPart,
RenderSupplierPart
} from './Company';
import { ModelType } from './ModelType';
@@ -41,6 +42,7 @@ const RendererLookup: EnumDictionary<
[ModelType.build]: RenderBuildOrder,
[ModelType.company]: RenderCompany,
[ModelType.contact]: RenderContact,
+ [ModelType.manufacturerpart]: RenderManufacturerPart,
[ModelType.owner]: RenderOwner,
[ModelType.part]: RenderPart,
[ModelType.partcategory]: RenderPartCategory,
@@ -54,8 +56,7 @@ const RendererLookup: EnumDictionary<
[ModelType.stockitem]: RenderStockItem,
[ModelType.stockhistory]: RenderStockItem,
[ModelType.supplierpart]: RenderSupplierPart,
- [ModelType.user]: RenderUser,
- [ModelType.manufacturerpart]: RenderPart
+ [ModelType.user]: RenderUser
};
// import { ApiFormFieldType } from "../forms/fields/ApiFormField";
diff --git a/src/frontend/src/components/settings/SettingItem.tsx b/src/frontend/src/components/settings/SettingItem.tsx
index 76a9609390..b92df30879 100644
--- a/src/frontend/src/components/settings/SettingItem.tsx
+++ b/src/frontend/src/components/settings/SettingItem.tsx
@@ -51,7 +51,6 @@ function SettingValue({
}
openModalApiForm({
- name: 'setting-edit',
url: settingsState.endpoint,
pk: setting.key,
method: 'PATCH',
diff --git a/src/frontend/src/components/tables/InvenTreeTable.tsx b/src/frontend/src/components/tables/InvenTreeTable.tsx
index 19be7e362f..4c51d4b19f 100644
--- a/src/frontend/src/components/tables/InvenTreeTable.tsx
+++ b/src/frontend/src/components/tables/InvenTreeTable.tsx
@@ -524,7 +524,11 @@ export function InvenTreeTable({
onRowClick={tableProps.onRowClick}
defaultColumnProps={{
noWrap: true,
- textAlignment: 'left'
+ textAlignment: 'left',
+ cellsStyle: {
+ // TODO: Need a better way of handling "wide" cells,
+ overflow: 'hidden'
+ }
}}
/>
diff --git a/src/frontend/src/components/tables/RowActions.tsx b/src/frontend/src/components/tables/RowActions.tsx
index 8a6df573bb..1c0392c6ab 100644
--- a/src/frontend/src/components/tables/RowActions.tsx
+++ b/src/frontend/src/components/tables/RowActions.tsx
@@ -2,7 +2,7 @@ import { t } from '@lingui/macro';
import { ActionIcon, Tooltip } from '@mantine/core';
import { Menu, Text } from '@mantine/core';
import { IconDots } from '@tabler/icons-react';
-import { ReactNode, useState } from 'react';
+import { ReactNode, useMemo, useState } from 'react';
import { notYetImplemented } from '../../functions/notifications';
@@ -12,9 +12,41 @@ export type RowAction = {
color?: string;
onClick?: () => void;
tooltip?: string;
- icon?: ReactNode;
+ hidden?: boolean;
};
+// Component for ediitng a row in a table
+export function RowEditAction({
+ onClick,
+ hidden
+}: {
+ onClick?: () => void;
+ hidden?: boolean;
+}): RowAction {
+ return {
+ title: t`Edit`,
+ color: 'green',
+ onClick: onClick,
+ hidden: hidden
+ };
+}
+
+// Component for deleting a row in a table
+export function RowDeleteAction({
+ onClick,
+ hidden
+}: {
+ onClick?: () => void;
+ hidden?: boolean;
+}): RowAction {
+ return {
+ title: t`Delete`,
+ color: 'red',
+ onClick: onClick,
+ hidden: hidden
+ };
+}
+
/**
* Component for displaying actions for a row in a table.
* Displays a simple dropdown menu with a list of actions.
@@ -39,8 +71,12 @@ export function RowActions({
const [opened, setOpened] = useState(false);
+ const visibleActions = useMemo(() => {
+ return actions.filter((action) => !action.hidden);
+ }, [actions]);
+
return (
- actions.length > 0 && (
+ visibleActions.length > 0 && (