diff --git a/src/frontend/src/components/buttons/ActionButton.tsx b/src/frontend/src/components/buttons/ActionButton.tsx
new file mode 100644
index 0000000000..9aeb65cb00
--- /dev/null
+++ b/src/frontend/src/components/buttons/ActionButton.tsx
@@ -0,0 +1,44 @@
+import { ActionIcon, Group, Tooltip } from '@mantine/core';
+import { ReactNode } from 'react';
+
+import { notYetImplemented } from '../../functions/notifications';
+
+export type ActionButtonProps = {
+ icon?: ReactNode;
+ text?: string;
+ color?: string;
+ tooltip?: string;
+ variant?: string;
+ size?: number;
+ disabled?: boolean;
+ onClick?: any;
+ hidden?: boolean;
+};
+
+/**
+ * Construct a simple action button with consistent styling
+ */
+export function ActionButton(props: ActionButtonProps) {
+ return (
+ !props.hidden && (
+
+
+
+ {props.icon}
+
+
+
+ )
+ );
+}
diff --git a/src/frontend/src/components/buttons/AddItemButton.tsx b/src/frontend/src/components/buttons/AddItemButton.tsx
new file mode 100644
index 0000000000..a9af3de8f5
--- /dev/null
+++ b/src/frontend/src/components/buttons/AddItemButton.tsx
@@ -0,0 +1,10 @@
+import { IconPlus } from '@tabler/icons-react';
+
+import { ActionButton, ActionButtonProps } from './ActionButton';
+
+/**
+ * A generic icon button which is used to add or create a new item
+ */
+export function AddItemButton(props: ActionButtonProps) {
+ return } />;
+}
diff --git a/src/frontend/src/components/items/ButtonMenu.tsx b/src/frontend/src/components/buttons/ButtonMenu.tsx
similarity index 100%
rename from src/frontend/src/components/items/ButtonMenu.tsx
rename to src/frontend/src/components/buttons/ButtonMenu.tsx
diff --git a/src/frontend/src/components/items/ActionButton.tsx b/src/frontend/src/components/items/ActionButton.tsx
deleted file mode 100644
index 48d7cd9b2a..0000000000
--- a/src/frontend/src/components/items/ActionButton.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { ActionIcon, Tooltip } from '@mantine/core';
-
-/**
- * Construct a simple action button with consistent styling
- */
-export function ActionButton({
- icon,
- color = 'black',
- tooltip = '',
- disabled = false,
- size = 18,
- onClick
-}: {
- icon: any;
- color?: string;
- tooltip?: string;
- variant?: string;
- size?: number;
- disabled?: boolean;
- onClick?: any;
-}) {
- return (
-
-
- {icon}
-
-
- );
-}
diff --git a/src/frontend/src/components/items/ActionDropdown.tsx b/src/frontend/src/components/items/ActionDropdown.tsx
index e8b937fcac..b5b9ce17c7 100644
--- a/src/frontend/src/components/items/ActionDropdown.tsx
+++ b/src/frontend/src/components/items/ActionDropdown.tsx
@@ -1,6 +1,13 @@
import { t } from '@lingui/macro';
import { ActionIcon, Menu, Tooltip } from '@mantine/core';
-import { IconQrcode } from '@tabler/icons-react';
+import {
+ IconCopy,
+ IconEdit,
+ IconLink,
+ IconQrcode,
+ IconTrash,
+ IconUnlink
+} from '@tabler/icons-react';
import { ReactNode, useMemo } from 'react';
import { notYetImplemented } from '../../functions/notifications';
@@ -81,3 +88,111 @@ export function BarcodeActionDropdown({
/>
);
}
+
+// Common action button for viewing a barcode
+export function ViewBarcodeAction({
+ disabled = false,
+ callback
+}: {
+ disabled?: boolean;
+ callback?: () => void;
+}): ActionDropdownItem {
+ return {
+ icon: ,
+ name: t`View`,
+ tooltip: t`View barcode`,
+ onClick: callback,
+ disabled: disabled
+ };
+}
+
+// Common action button for linking a custom barcode
+export function LinkBarcodeAction({
+ disabled = false,
+ callback
+}: {
+ disabled?: boolean;
+ callback?: () => void;
+}): ActionDropdownItem {
+ return {
+ icon: ,
+ name: t`Link Barcode`,
+ tooltip: t`Link custom barcode`,
+ onClick: callback,
+ disabled: disabled
+ };
+}
+
+// Common action button for un-linking a custom barcode
+export function UnlinkBarcodeAction({
+ disabled = false,
+ callback
+}: {
+ disabled?: boolean;
+ callback?: () => void;
+}): ActionDropdownItem {
+ return {
+ icon: ,
+ name: t`Unlink Barcode`,
+ tooltip: t`Unlink custom barcode`,
+ onClick: callback,
+ disabled: disabled
+ };
+}
+
+// Common action button for editing an item
+export function EditItemAction({
+ disabled = false,
+ tooltip,
+ callback
+}: {
+ disabled?: boolean;
+ tooltip?: string;
+ callback?: () => void;
+}): ActionDropdownItem {
+ return {
+ icon: ,
+ name: t`Edit`,
+ tooltip: tooltip ?? `Edit item`,
+ onClick: callback,
+ disabled: disabled
+ };
+}
+
+// Common action button for deleting an item
+export function DeleteItemAction({
+ disabled = false,
+ tooltip,
+ callback
+}: {
+ disabled?: boolean;
+ tooltip?: string;
+ callback?: () => void;
+}): ActionDropdownItem {
+ return {
+ icon: ,
+ name: t`Delete`,
+ tooltip: tooltip ?? t`Delete item`,
+ onClick: callback,
+ disabled: disabled
+ };
+}
+
+// Common action button for duplicating an item
+export function DuplicateItemAction({
+ disabled = false,
+ tooltip,
+ callback
+}: {
+ disabled?: boolean;
+ tooltip?: string;
+ callback?: () => void;
+}): ActionDropdownItem {
+ return {
+ icon: ,
+ name: t`Duplicate`,
+ tooltip: tooltip ?? t`Duplicate item`,
+ onClick: callback,
+ disabled: disabled
+ };
+}
diff --git a/src/frontend/src/components/items/ProgressBar.tsx b/src/frontend/src/components/items/ProgressBar.tsx
new file mode 100644
index 0000000000..186051b1b5
--- /dev/null
+++ b/src/frontend/src/components/items/ProgressBar.tsx
@@ -0,0 +1,39 @@
+import { Progress, Stack, Text } from '@mantine/core';
+import { useMemo } from 'react';
+
+export type ProgressBarProps = {
+ value: number;
+ maximum?: number;
+ label?: string;
+ progressLabel?: boolean;
+};
+
+/**
+ * A progress bar element, built on mantine.Progress
+ * The color of the bar is determined based on the value
+ */
+export function ProgressBar(props: ProgressBarProps) {
+ const progress = useMemo(() => {
+ let maximum = props.maximum ?? 100;
+ let value = Math.max(props.value, 0);
+
+ // Calculate progress as a percentage of the maximum value
+ return Math.min(100, (value / maximum) * 100);
+ }, [props]);
+
+ return (
+
+ {props.progressLabel && (
+
+ {props.value} / {props.maximum}
+
+ )}
+
+ );
+}
diff --git a/src/frontend/src/components/render/Company.tsx b/src/frontend/src/components/render/Company.tsx
index a1d2b40e87..23771f0ec2 100644
--- a/src/frontend/src/components/render/Company.tsx
+++ b/src/frontend/src/components/render/Company.tsx
@@ -52,19 +52,19 @@ export function RenderContact({ instance }: { instance: any }): ReactNode {
* Inline rendering of a single SupplierPart instance
*/
export function RenderSupplierPart({ instance }: { instance: any }): ReactNode {
- // TODO: Handle image
// TODO: handle URL
let supplier = instance.supplier_detail ?? {};
let part = instance.part_detail ?? {};
- let text = instance.SKU;
-
- if (supplier.name) {
- text = `${supplier.name} | ${text}`;
- }
-
- return ;
+ return (
+
+ );
}
/**
diff --git a/src/frontend/src/components/render/User.tsx b/src/frontend/src/components/render/User.tsx
index 6bad2a3011..8f77476cde 100644
--- a/src/frontend/src/components/render/User.tsx
+++ b/src/frontend/src/components/render/User.tsx
@@ -5,14 +5,16 @@ import { RenderInlineModel } from './Instance';
export function RenderOwner({ instance }: { instance: any }): ReactNode {
// TODO: Icon based on user / group status?
- return ;
+ return instance && ;
}
export function RenderUser({ instance }: { instance: any }): ReactNode {
return (
-
+ instance && (
+
+ )
);
}
diff --git a/src/frontend/src/components/renderers/PurchaseOrderRenderer.tsx b/src/frontend/src/components/renderers/PurchaseOrderRenderer.tsx
index 1b0272552b..d59b6789e1 100644
--- a/src/frontend/src/components/renderers/PurchaseOrderRenderer.tsx
+++ b/src/frontend/src/components/renderers/PurchaseOrderRenderer.tsx
@@ -17,7 +17,7 @@ export const PurchaseOrderRenderer = ({ pk }: { pk: string }) => {
return (
{t`Select Columns`}
{columns
- .filter((col) => col.switchable)
+ .filter((col) => col.switchable ?? true)
.map((col) => (
[],
onRowClick: (record: any, index: number, event: any) => {}
};
@@ -115,7 +114,7 @@ export function InvenTreeTable({
// Check if any columns are switchable (can be hidden)
const hasSwitchableColumns = columns.some(
- (col: TableColumn) => col.switchable
+ (col: TableColumn) => col.switchable ?? true
);
// A list of hidden columns, saved to local storage
@@ -142,7 +141,7 @@ export function InvenTreeTable({
let cols = columns.map((col) => {
let hidden: boolean = col.hidden ?? false;
- if (col.switchable) {
+ if (col.switchable ?? true) {
hidden = hiddenColumns.includes(col.accessor);
}
@@ -156,10 +155,19 @@ export function InvenTreeTable({
if (tableProps.rowActions) {
cols.push({
accessor: 'actions',
- title: '',
+ title: ' ',
hidden: false,
switchable: false,
- width: 48,
+ width: 50,
+ cellsStyle: {
+ position: 'sticky',
+ right: 0,
+ // TODO: Use the theme color to set the background color
+ backgroundColor: '#FFF',
+ // TODO: Use the scroll area callbacks to determine if we need to display a "shadow"
+ borderLeft: '1px solid #DDD',
+ padding: '3px'
+ },
render: function (record: any) {
return (
)}
- {tableProps.enableDownload && (
-
- )}
@@ -488,6 +490,12 @@ export function InvenTreeTable({
)}
+ {tableProps.enableDownload && (
+
+ )}
{filtersVisible && (
@@ -504,7 +512,7 @@ export function InvenTreeTable({
highlightOnHover
loaderVariant="dots"
idAccessor={tableProps.idAccessor}
- minHeight={200}
+ minHeight={300}
totalRecords={recordCount}
recordsPerPage={tableProps.pageSize ?? defaultPageSize}
page={page}
diff --git a/src/frontend/src/components/tables/RowActions.tsx b/src/frontend/src/components/tables/RowActions.tsx
index 1c0392c6ab..a16de60c17 100644
--- a/src/frontend/src/components/tables/RowActions.tsx
+++ b/src/frontend/src/components/tables/RowActions.tsx
@@ -15,7 +15,23 @@ export type RowAction = {
hidden?: boolean;
};
-// Component for ediitng a row in a table
+// Component for duplicating a row in a table
+export function RowDuplicateAction({
+ onClick,
+ hidden
+}: {
+ onClick?: () => void;
+ hidden?: boolean;
+}): RowAction {
+ return {
+ title: t`Duplicate`,
+ color: 'green',
+ onClick: onClick,
+ hidden: hidden
+ };
+}
+
+// Component for editing a row in a table
export function RowEditAction({
onClick,
hidden
@@ -25,7 +41,7 @@ export function RowEditAction({
}): RowAction {
return {
title: t`Edit`,
- color: 'green',
+ color: 'blue',
onClick: onClick,
hidden: hidden
};
diff --git a/src/frontend/src/components/tables/TableHoverCard.tsx b/src/frontend/src/components/tables/TableHoverCard.tsx
index d91fe3407a..03499f585d 100644
--- a/src/frontend/src/components/tables/TableHoverCard.tsx
+++ b/src/frontend/src/components/tables/TableHoverCard.tsx
@@ -1,5 +1,6 @@
-import { Divider, Group, HoverCard, Stack } from '@mantine/core';
+import { Divider, Group, HoverCard, Stack, Text } from '@mantine/core';
import { IconInfoCircle } from '@tabler/icons-react';
+import { ReactNode } from 'react';
/*
* A custom hovercard element for displaying extra information in a table cell.
@@ -12,7 +13,7 @@ export function TableHoverCard({
title // The title of the hovercard
}: {
value: any;
- extra?: any;
+ extra?: ReactNode;
title?: string;
}) {
// If no extra information presented, just return the raw value
@@ -32,7 +33,7 @@ export function TableHoverCard({
- {title}
+ {title}
{extra}
diff --git a/src/frontend/src/components/tables/bom/BomTable.tsx b/src/frontend/src/components/tables/bom/BomTable.tsx
index cf05870946..44db315b10 100644
--- a/src/frontend/src/components/tables/bom/BomTable.tsx
+++ b/src/frontend/src/components/tables/bom/BomTable.tsx
@@ -1,5 +1,5 @@
import { t } from '@lingui/macro';
-import { Stack, Text } from '@mantine/core';
+import { Text } from '@mantine/core';
import { ReactNode, useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
@@ -51,12 +51,11 @@ export function BomTable({
{
accessor: 'description',
title: t`Description`,
- switchable: true,
render: (row) => row?.sub_part_detail?.description
},
{
accessor: 'reference',
- switchable: true,
+
title: t`Reference`
},
{
@@ -66,7 +65,7 @@ export function BomTable({
{
accessor: 'substitutes',
title: t`Substitutes`,
- switchable: true,
+
render: (row) => {
let substitutes = row.substitutes ?? [];
@@ -80,7 +79,7 @@ export function BomTable({
{
accessor: 'optional',
title: t`Optional`,
- switchable: true,
+
sortable: true,
render: (row) => {
return ;
@@ -89,7 +88,7 @@ export function BomTable({
{
accessor: 'consumable',
title: t`Consumable`,
- switchable: true,
+
sortable: true,
render: (row) => {
return ;
@@ -98,7 +97,7 @@ export function BomTable({
{
accessor: 'allow_variants',
title: t`Allow Variants`,
- switchable: true,
+
sortable: true,
render: (row) => {
return ;
@@ -107,7 +106,7 @@ export function BomTable({
{
accessor: 'inherited',
title: t`Gets Inherited`,
- switchable: true,
+
sortable: true,
render: (row) => {
// TODO: Update complexity here
@@ -117,7 +116,7 @@ export function BomTable({
{
accessor: 'price_range',
title: t`Price Range`,
- switchable: true,
+
sortable: false,
render: (row) => {
let min_price = row.pricing_min || row.pricing_max;
@@ -130,7 +129,7 @@ export function BomTable({
{
accessor: 'available_stock',
title: t`Available`,
- switchable: true,
+
render: (row) => {
let extra: ReactNode[] = [];
@@ -164,9 +163,7 @@ export function BomTable({
return (
0 ? {extra} : null
- }
+ extra={extra}
title={t`Available Stock`}
/>
);
@@ -175,7 +172,7 @@ export function BomTable({
{
accessor: 'can_build',
title: t`Can Build`,
- switchable: true,
+
sortable: true // TODO: Custom sorting via API
// TODO: Reference bom.js for canBuildQuantity method
},
diff --git a/src/frontend/src/components/tables/bom/UsedInTable.tsx b/src/frontend/src/components/tables/bom/UsedInTable.tsx
index 02886df668..486c69e704 100644
--- a/src/frontend/src/components/tables/bom/UsedInTable.tsx
+++ b/src/frontend/src/components/tables/bom/UsedInTable.tsx
@@ -73,8 +73,7 @@ export function UsedInTable({
{
accessor: 'reference',
title: t`Reference`,
- sortable: true,
- switchable: true
+ sortable: true
}
];
}, [partId]);
diff --git a/src/frontend/src/components/tables/build/BuildOrderTable.tsx b/src/frontend/src/components/tables/build/BuildOrderTable.tsx
index e106cc5cde..2de0e78ad3 100644
--- a/src/frontend/src/components/tables/build/BuildOrderTable.tsx
+++ b/src/frontend/src/components/tables/build/BuildOrderTable.tsx
@@ -1,16 +1,19 @@
import { t } from '@lingui/macro';
-import { Progress } from '@mantine/core';
+import { Text } from '@mantine/core';
import { useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTableRefresh } from '../../../hooks/TableRefresh';
import { ApiPaths, apiUrl } from '../../../states/ApiState';
import { ThumbnailHoverCard } from '../../images/Thumbnail';
+import { ProgressBar } from '../../items/ProgressBar';
import { ModelType } from '../../render/ModelType';
+import { RenderOwner, RenderUser } from '../../render/User';
import { TableStatusRenderer } from '../../renderers/StatusRenderer';
import { TableColumn } from '../Column';
import { TableFilter } from '../Filter';
import { InvenTreeTable } from '../InvenTreeTable';
+import { TableHoverCard } from '../TableHoverCard';
/**
* Construct a list of columns for the build order table
@@ -20,12 +23,13 @@ function buildOrderTableColumns(): TableColumn[] {
{
accessor: 'reference',
sortable: true,
+ switchable: false,
title: t`Reference`
- // TODO: Link to the build order detail page
},
{
accessor: 'part',
sortable: true,
+ switchable: false,
title: t`Part`,
render: (record: any) => {
let part = record.part_detail;
@@ -44,86 +48,87 @@ function buildOrderTableColumns(): TableColumn[] {
{
accessor: 'title',
sortable: false,
- title: t`Description`,
- switchable: true
- },
- {
- accessor: 'project_code',
- title: t`Project Code`,
- sortable: true,
- switchable: false,
- hidden: true
- // TODO: Hide this if project code is not enabled
- // TODO: Custom render function here
+ title: t`Description`
},
{
accessor: 'quantity',
sortable: true,
title: t`Quantity`,
- switchable: true
+ switchable: false
},
{
accessor: 'completed',
sortable: true,
title: t`Completed`,
- render: (record: any) => {
- let progress =
- record.quantity <= 0 ? 0 : (100 * record.completed) / record.quantity;
- return (
-
- );
- }
+ render: (record: any) => (
+
+ )
},
{
accessor: 'status',
sortable: true,
title: t`Status`,
- switchable: true,
+
render: TableStatusRenderer(ModelType.build)
},
+ {
+ accessor: 'project_code',
+ title: t`Project Code`,
+ sortable: true,
+ // TODO: Hide this if project code is not enabled
+ render: (record: any) => {
+ let project = record.project_code_detail;
+
+ return project ? (
+ {project.description}}
+ />
+ ) : (
+ '-'
+ );
+ }
+ },
{
accessor: 'priority',
title: t`Priority`,
- sortable: true,
- switchable: true
+ sortable: true
},
{
accessor: 'creation_date',
sortable: true,
- title: t`Created`,
- switchable: true
+ title: t`Created`
},
{
accessor: 'target_date',
sortable: true,
- title: t`Target Date`,
- switchable: true
+ title: t`Target Date`
},
{
accessor: 'completion_date',
sortable: true,
- title: t`Completed`,
- switchable: true
+ title: t`Completed`
},
{
accessor: 'issued_by',
sortable: true,
title: t`Issued By`,
- switchable: true
- // TODO: custom render function
+ render: (record: any) => (
+
+ )
},
{
accessor: 'responsible',
sortable: true,
title: t`Responsible`,
- switchable: true
- // TODO: custom render function
+ render: (record: any) => (
+
+ )
}
];
}
diff --git a/src/frontend/src/components/tables/general/AttachmentTable.tsx b/src/frontend/src/components/tables/general/AttachmentTable.tsx
index f8b9dc89c5..5519e0870a 100644
--- a/src/frontend/src/components/tables/general/AttachmentTable.tsx
+++ b/src/frontend/src/components/tables/general/AttachmentTable.tsx
@@ -45,7 +45,7 @@ function attachmentTableColumns(): TableColumn[] {
accessor: 'comment',
title: t`Comment`,
sortable: false,
- switchable: true,
+
render: function (record: any) {
return record.comment;
}
@@ -54,7 +54,7 @@ function attachmentTableColumns(): TableColumn[] {
accessor: 'uploaded',
title: t`Uploaded`,
sortable: false,
- switchable: true,
+
render: function (record: any) {
return (
diff --git a/src/frontend/src/components/tables/general/CompanyTable.tsx b/src/frontend/src/components/tables/general/CompanyTable.tsx
index 73a40c1b12..77908caea4 100644
--- a/src/frontend/src/components/tables/general/CompanyTable.tsx
+++ b/src/frontend/src/components/tables/general/CompanyTable.tsx
@@ -45,14 +45,12 @@ export function CompanyTable({
{
accessor: 'description',
title: t`Description`,
- sortable: false,
- switchable: true
+ sortable: false
},
{
accessor: 'website',
title: t`Website`,
- sortable: false,
- switchable: true
+ sortable: false
}
];
}, []);
diff --git a/src/frontend/src/components/tables/part/PartCategoryTable.tsx b/src/frontend/src/components/tables/part/PartCategoryTable.tsx
index a5d14a2dea..c2c7ad55ed 100644
--- a/src/frontend/src/components/tables/part/PartCategoryTable.tsx
+++ b/src/frontend/src/components/tables/part/PartCategoryTable.tsx
@@ -26,20 +26,17 @@ export function PartCategoryTable({ params = {} }: { params?: any }) {
{
accessor: 'description',
title: t`Description`,
- sortable: false,
- switchable: true
+ sortable: false
},
{
accessor: 'pathstring',
title: t`Path`,
- sortable: false,
- switchable: true
+ sortable: false
},
{
accessor: 'part_count',
title: t`Parts`,
- sortable: true,
- switchable: true
+ sortable: true
}
];
}, []);
diff --git a/src/frontend/src/components/tables/part/PartParameterTable.tsx b/src/frontend/src/components/tables/part/PartParameterTable.tsx
index 2823a45791..855f00e763 100644
--- a/src/frontend/src/components/tables/part/PartParameterTable.tsx
+++ b/src/frontend/src/components/tables/part/PartParameterTable.tsx
@@ -1,6 +1,5 @@
import { t } from '@lingui/macro';
-import { ActionIcon, Group, Text, Tooltip } from '@mantine/core';
-import { IconTextPlus } from '@tabler/icons-react';
+import { Group, Text } from '@mantine/core';
import { useCallback, useMemo } from 'react';
import {
@@ -10,6 +9,7 @@ import {
} from '../../../functions/forms';
import { useTableRefresh } from '../../../hooks/TableRefresh';
import { ApiPaths, apiUrl } from '../../../states/ApiState';
+import { AddItemButton } from '../../buttons/AddItemButton';
import { Thumbnail } from '../../images/Thumbnail';
import { YesNoButton } from '../../items/YesNoButton';
import { TableColumn } from '../Column';
@@ -27,7 +27,7 @@ export function PartParameterTable({ partId }: { partId: any }) {
{
accessor: 'part',
title: t`Part`,
- switchable: true,
+
sortable: true,
render: function (record: any) {
let part = record?.part_detail ?? {};
@@ -59,7 +59,7 @@ export function PartParameterTable({ partId }: { partId: any }) {
accessor: 'description',
title: t`Description`,
sortable: false,
- switchable: true,
+
render: (record) => record.template_detail?.description
},
{
@@ -86,7 +86,7 @@ export function PartParameterTable({ partId }: { partId: any }) {
{
accessor: 'units',
title: t`Units`,
- switchable: true,
+
sortable: true,
render: (record) => record.template_detail?.units
}
@@ -174,11 +174,7 @@ export function PartParameterTable({ partId }: { partId: any }) {
// TODO: Hide if user does not have permission to edit parts
actions.push(
-
-
-
-
-
+
);
return actions;
diff --git a/src/frontend/src/components/tables/part/PartTable.tsx b/src/frontend/src/components/tables/part/PartTable.tsx
index fb2466d1d8..49221ce43a 100644
--- a/src/frontend/src/components/tables/part/PartTable.tsx
+++ b/src/frontend/src/components/tables/part/PartTable.tsx
@@ -1,5 +1,5 @@
import { t } from '@lingui/macro';
-import { Group, Stack, Text } from '@mantine/core';
+import { Group, Text } from '@mantine/core';
import { ReactNode, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
@@ -35,26 +35,23 @@ function partTableColumns(): TableColumn[] {
{
accessor: 'IPN',
title: t`IPN`,
- sortable: true,
- switchable: true
+ sortable: true
},
{
accessor: 'units',
sortable: true,
- title: t`Units`,
- switchable: true
+ title: t`Units`
},
{
accessor: 'description',
title: t`Description`,
- sortable: true,
- switchable: true
+ sortable: true
},
{
accessor: 'category',
title: t`Category`,
sortable: true,
- switchable: true,
+
render: function (record: any) {
// TODO: Link to the category detail page
return shortenString({
@@ -66,7 +63,7 @@ function partTableColumns(): TableColumn[] {
accessor: 'total_in_stock',
title: t`Stock`,
sortable: true,
- switchable: true,
+
render: (record) => {
let extra: ReactNode[] = [];
@@ -143,7 +140,7 @@ function partTableColumns(): TableColumn[] {
}
title={t`Stock Information`}
- extra={extra.length > 0 && {extra}}
+ extra={extra}
/>
);
}
@@ -152,7 +149,7 @@ function partTableColumns(): TableColumn[] {
accessor: 'price_range',
title: t`Price Range`,
sortable: false,
- switchable: true,
+
render: function (record: any) {
// TODO: Render price range
return '-- price --';
@@ -160,8 +157,7 @@ function partTableColumns(): TableColumn[] {
},
{
accessor: 'link',
- title: t`Link`,
- switchable: true
+ title: t`Link`
}
];
}
diff --git a/src/frontend/src/components/tables/plugin/PluginListTable.tsx b/src/frontend/src/components/tables/plugin/PluginListTable.tsx
index 0c171c58b7..20a85cce0e 100644
--- a/src/frontend/src/components/tables/plugin/PluginListTable.tsx
+++ b/src/frontend/src/components/tables/plugin/PluginListTable.tsx
@@ -68,7 +68,7 @@ export function PluginListTable({ props }: { props: InvenTreeTableProps }) {
accessor: 'meta.description',
title: t`Description`,
sortable: false,
- switchable: true,
+
render: function (record: any) {
if (record.active) {
return record.meta.description;
@@ -80,15 +80,14 @@ export function PluginListTable({ props }: { props: InvenTreeTableProps }) {
{
accessor: 'meta.version',
title: t`Version`,
- sortable: false,
- switchable: true
+ sortable: false
+
// TODO: Display date information if available
},
{
accessor: 'meta.author',
title: 'Author',
- sortable: false,
- switchable: true
+ sortable: false
}
],
[]
diff --git a/src/frontend/src/components/tables/purchasing/PurchaseOrderLineItemTable.tsx b/src/frontend/src/components/tables/purchasing/PurchaseOrderLineItemTable.tsx
new file mode 100644
index 0000000000..bd264a1ba8
--- /dev/null
+++ b/src/frontend/src/components/tables/purchasing/PurchaseOrderLineItemTable.tsx
@@ -0,0 +1,266 @@
+import { t } from '@lingui/macro';
+import { Text } from '@mantine/core';
+import { IconSquareArrowRight } from '@tabler/icons-react';
+import { useCallback, useMemo } from 'react';
+
+import { ProgressBar } from '../../../components/items/ProgressBar';
+import { purchaseOrderLineItemFields } from '../../../forms/PurchaseOrderForms';
+import { openCreateApiForm, openEditApiForm } from '../../../functions/forms';
+import { useTableRefresh } from '../../../hooks/TableRefresh';
+import { ApiPaths, apiUrl } from '../../../states/ApiState';
+import { useUserState } from '../../../states/UserState';
+import { ActionButton } from '../../buttons/ActionButton';
+import { AddItemButton } from '../../buttons/AddItemButton';
+import { Thumbnail } from '../../images/Thumbnail';
+import { InvenTreeTable } from '../InvenTreeTable';
+import {
+ RowDeleteAction,
+ RowDuplicateAction,
+ RowEditAction
+} from '../RowActions';
+import { TableHoverCard } from '../TableHoverCard';
+
+/*
+ * Display a table of purchase order line items, for a specific order
+ */
+export function PurchaseOrderLineItemTable({
+ orderId,
+ params
+}: {
+ orderId: number;
+ params?: any;
+}) {
+ const { tableKey, refreshTable } = useTableRefresh(
+ 'purchase-order-line-item'
+ );
+
+ const user = useUserState();
+
+ const rowActions = useCallback(
+ (record: any) => {
+ // TODO: Hide certain actions if user does not have required permissions
+
+ let received = (record?.received ?? 0) >= (record?.quantity ?? 0);
+
+ return [
+ {
+ hidden: received,
+ title: t`Receive`,
+ tooltip: t`Receive line item`,
+ color: 'green'
+ },
+ RowEditAction({
+ onClick: () => {
+ let supplier = record?.supplier_part_detail?.supplier;
+
+ if (!supplier) {
+ return;
+ }
+
+ let fields = purchaseOrderLineItemFields({
+ supplierId: supplier
+ });
+
+ openEditApiForm({
+ url: ApiPaths.purchase_order_line_list,
+ pk: record.pk,
+ title: t`Edit Line Item`,
+ fields: fields,
+ onFormSuccess: refreshTable,
+ successMessage: t`Line item updated`
+ });
+ }
+ }),
+ RowDuplicateAction({}),
+ RowDeleteAction({})
+ ];
+ },
+ [orderId, user]
+ );
+
+ const tableColumns = useMemo(() => {
+ return [
+ {
+ accessor: 'part',
+ title: t`Part`,
+ sortable: true,
+ switchable: false,
+ render: (record: any) => {
+ return (
+
+ );
+ }
+ },
+ {
+ accessor: 'description',
+ title: t`Part Description`,
+
+ sortable: false,
+ render: (record: any) => record?.part_detail?.description
+ },
+ {
+ accessor: 'reference',
+ title: t`Reference`,
+ sortable: true
+ },
+ {
+ accessor: 'quantity',
+ title: t`Quantity`,
+ sortable: true,
+ switchable: false,
+ render: (record: any) => {
+ let part = record?.part_detail;
+ let supplier_part = record?.supplier_part_detail ?? {};
+ let extra = [];
+
+ if (supplier_part.pack_quantity_native != 1) {
+ let total = record.quantity * supplier_part.pack_quantity_native;
+
+ extra.push(
+
+ {t`Pack Quantity`}: {supplier_part.pack_quantity}
+
+ );
+
+ extra.push(
+
+ {t`Total Quantity`}: {total}
+ {part.units}
+
+ );
+ }
+
+ return (
+
+ );
+ }
+ },
+ {
+ accessor: 'received',
+ title: t`Received`,
+ sortable: false,
+
+ render: (record: any) => (
+
+ )
+ },
+ {
+ accessor: 'pack_quantity',
+ sortable: false,
+
+ title: t`Pack Quantity`,
+ render: (record: any) => record?.supplier_part_detail?.pack_quantity
+ },
+ {
+ accessor: 'SKU',
+ title: t`Supplier Code`,
+ switchable: false,
+ sortable: true
+ },
+ {
+ accessor: 'supplier_link',
+ title: t`Supplier Link`,
+
+ sortable: false,
+ render: (record: any) => record?.supplier_part_detail?.link
+ },
+ {
+ accessor: 'MPN',
+ title: t`Manufacturer Code`,
+ sortable: true,
+
+ render: (record: any) =>
+ record?.supplier_part_detail?.manufacturer_part_detail?.MPN
+ },
+
+ {
+ accessor: 'purchase_price',
+ title: t`Unit Price`,
+ sortable: true
+
+ // TODO: custom renderer
+ },
+ {
+ accessor: 'total_price',
+ title: t`Total Price`,
+ sortable: true
+
+ // TODO: custom renderer
+ },
+ {
+ accessor: 'target_date',
+ title: t`Target Date`,
+ sortable: true
+ },
+ {
+ accessor: 'destination',
+ title: t`Destination`,
+ sortable: false
+
+ // TODO: Custom renderer
+ },
+ {
+ accessor: 'notes',
+ title: t`Notes`
+ },
+ {
+ accessor: 'link',
+ title: t`Link`
+
+ // TODO: custom renderer
+ }
+ ];
+ }, [orderId, user]);
+
+ const addLine = useCallback(() => {
+ openCreateApiForm({
+ url: ApiPaths.purchase_order_line_list,
+ title: t`Add Line Item`,
+ fields: purchaseOrderLineItemFields({}),
+ onFormSuccess: refreshTable,
+ successMessage: t`Line item added`
+ });
+ }, []);
+
+ // Custom table actions
+ const tableActions = useMemo(() => {
+ return [
+ ,
+ } />
+ ];
+ }, [orderId, user]);
+
+ return (
+
+ );
+}
diff --git a/src/frontend/src/components/tables/purchasing/PurchaseOrderTable.tsx b/src/frontend/src/components/tables/purchasing/PurchaseOrderTable.tsx
index 9c7247082c..a0343358c1 100644
--- a/src/frontend/src/components/tables/purchasing/PurchaseOrderTable.tsx
+++ b/src/frontend/src/components/tables/purchasing/PurchaseOrderTable.tsx
@@ -33,8 +33,7 @@ export function PurchaseOrderTable({ params }: { params?: any }) {
},
{
accessor: 'description',
- title: t`Description`,
- switchable: true
+ title: t`Description`
},
{
accessor: 'supplier__name',
@@ -54,20 +53,19 @@ export function PurchaseOrderTable({ params }: { params?: any }) {
},
{
accessor: 'supplier_reference',
- title: t`Supplier Reference`,
- switchable: true
+ title: t`Supplier Reference`
},
{
accessor: 'project_code',
- title: t`Project Code`,
- switchable: true
+ title: t`Project Code`
+
// TODO: Custom project code formatter
},
{
accessor: 'status',
title: t`Status`,
sortable: true,
- switchable: true,
+
render: (record: any) =>
StatusRenderer({
status: record.status,
@@ -76,34 +74,33 @@ export function PurchaseOrderTable({ params }: { params?: any }) {
},
{
accessor: 'creation_date',
- title: t`Created`,
- switchable: true
+ title: t`Created`
+
// TODO: Custom date formatter
},
{
accessor: 'target_date',
- title: t`Target Date`,
- switchable: true
+ title: t`Target Date`
+
// TODO: Custom date formatter
},
{
accessor: 'line_items',
title: t`Line Items`,
- sortable: true,
- switchable: true
+ sortable: true
},
{
accessor: 'total_price',
title: t`Total Price`,
- sortable: true,
- switchable: true
+ sortable: true
+
// TODO: Custom money formatter
},
{
accessor: 'responsible',
title: t`Responsible`,
- sortable: true,
- switchable: true
+ sortable: true
+
// TODO: custom 'owner' formatter
}
];
diff --git a/src/frontend/src/components/tables/purchasing/SupplierPartTable.tsx b/src/frontend/src/components/tables/purchasing/SupplierPartTable.tsx
index 5b2c6723f2..be206a9400 100644
--- a/src/frontend/src/components/tables/purchasing/SupplierPartTable.tsx
+++ b/src/frontend/src/components/tables/purchasing/SupplierPartTable.tsx
@@ -1,6 +1,5 @@
import { t } from '@lingui/macro';
-import { ActionIcon, Stack, Text, Tooltip } from '@mantine/core';
-import { IconCirclePlus } from '@tabler/icons-react';
+import { Text } from '@mantine/core';
import { ReactNode, useCallback, useMemo } from 'react';
import { supplierPartFields } from '../../../forms/CompanyForms';
@@ -12,6 +11,7 @@ import {
import { useTableRefresh } from '../../../hooks/TableRefresh';
import { ApiPaths, apiUrl } from '../../../states/ApiState';
import { useUserState } from '../../../states/UserState';
+import { AddItemButton } from '../../buttons/AddItemButton';
import { Thumbnail } from '../../images/Thumbnail';
import { TableColumn } from '../Column';
import { InvenTreeTable } from '../InvenTreeTable';
@@ -66,12 +66,11 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
{
accessor: 'description',
title: t`Description`,
- sortable: false,
- switchable: true
+ sortable: false
},
{
accessor: 'manufacturer',
- switchable: true,
+
sortable: true,
title: t`Manufacturer`,
render: (record: any) => {
@@ -87,7 +86,7 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
},
{
accessor: 'MPN',
- switchable: true,
+
sortable: true,
title: t`MPN`,
render: (record: any) => record?.manufacturer_part_detail?.MPN
@@ -95,20 +94,18 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
{
accessor: 'in_stock',
title: t`In Stock`,
- sortable: true,
- switchable: true
+ sortable: true
},
{
accessor: 'packaging',
title: t`Packaging`,
- sortable: true,
- switchable: true
+ sortable: true
},
{
accessor: 'pack_quantity',
title: t`Pack Quantity`,
sortable: true,
- switchable: true,
+
render: (record: any) => {
let part = record?.part_detail ?? {};
@@ -125,7 +122,7 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
return (
0 && {extra}}
+ extra={extra}
title={t`Pack Quantity`}
/>
);
@@ -134,21 +131,20 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
{
accessor: 'link',
title: t`Link`,
- sortable: false,
- switchable: true
+ sortable: false
+
// TODO: custom link renderer?
},
{
accessor: 'note',
title: t`Notes`,
- sortable: false,
- switchable: true
+ sortable: false
},
{
accessor: 'available',
title: t`Availability`,
sortable: true,
- switchable: true,
+
render: (record: any) => {
let extra = [];
@@ -160,12 +156,7 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
);
}
- return (
- 0 && {extra}}
- />
- );
+ return ;
}
}
];
@@ -191,12 +182,7 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
// TODO: Hide actions based on user permissions
return [
- // TODO: Refactor this component out to something reusable
-
-
-
-
-
+
];
}, [user]);
diff --git a/src/frontend/src/components/tables/sales/ReturnOrderTable.tsx b/src/frontend/src/components/tables/sales/ReturnOrderTable.tsx
index 73d6e9174c..18f298d8ba 100644
--- a/src/frontend/src/components/tables/sales/ReturnOrderTable.tsx
+++ b/src/frontend/src/components/tables/sales/ReturnOrderTable.tsx
@@ -29,8 +29,7 @@ export function ReturnOrderTable({ params }: { params?: any }) {
},
{
accessor: 'description',
- title: t`Description`,
- switchable: true
+ title: t`Description`
},
{
accessor: 'customer__name',
@@ -50,20 +49,19 @@ export function ReturnOrderTable({ params }: { params?: any }) {
},
{
accessor: 'customer_reference',
- title: t`Customer Reference`,
- switchable: true
+ title: t`Customer Reference`
},
{
accessor: 'project_code',
- title: t`Project Code`,
- switchable: true
+ title: t`Project Code`
+
// TODO: Custom formatter
},
{
accessor: 'status',
title: t`Status`,
sortable: true,
- switchable: true,
+
render: TableStatusRenderer(ModelType.returnorder)
}
// TODO: Creation date
diff --git a/src/frontend/src/components/tables/sales/SalesOrderTable.tsx b/src/frontend/src/components/tables/sales/SalesOrderTable.tsx
index 505d1f3091..48cce5cc95 100644
--- a/src/frontend/src/components/tables/sales/SalesOrderTable.tsx
+++ b/src/frontend/src/components/tables/sales/SalesOrderTable.tsx
@@ -30,8 +30,7 @@ export function SalesOrderTable({ params }: { params?: any }) {
},
{
accessor: 'description',
- title: t`Description`,
- switchable: true
+ title: t`Description`
},
{
accessor: 'customer__name',
@@ -51,20 +50,19 @@ export function SalesOrderTable({ params }: { params?: any }) {
},
{
accessor: 'customer_reference',
- title: t`Customer Reference`,
- switchable: true
+ title: t`Customer Reference`
},
{
accessor: 'project_code',
- title: t`Project Code`,
- switchable: true
+ title: t`Project Code`
+
// TODO: Custom formatter
},
{
accessor: 'status',
title: t`Status`,
sortable: true,
- switchable: true,
+
render: TableStatusRenderer(ModelType.salesorder)
}
diff --git a/src/frontend/src/components/tables/settings/CustomUnitsTable.tsx b/src/frontend/src/components/tables/settings/CustomUnitsTable.tsx
index ce9a62b32d..30f861c83b 100644
--- a/src/frontend/src/components/tables/settings/CustomUnitsTable.tsx
+++ b/src/frontend/src/components/tables/settings/CustomUnitsTable.tsx
@@ -1,6 +1,5 @@
import { t } from '@lingui/macro';
-import { ActionIcon, Text, Tooltip } from '@mantine/core';
-import { IconCirclePlus } from '@tabler/icons-react';
+import { Text } from '@mantine/core';
import { useCallback, useMemo } from 'react';
import {
@@ -10,6 +9,7 @@ import {
} from '../../../functions/forms';
import { useTableRefresh } from '../../../hooks/TableRefresh';
import { ApiPaths, apiUrl } from '../../../states/ApiState';
+import { AddItemButton } from '../../buttons/AddItemButton';
import { TableColumn } from '../Column';
import { InvenTreeTable } from '../InvenTreeTable';
import { RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
@@ -96,11 +96,8 @@ export function CustomUnitsTable() {
let actions = [];
actions.push(
-
-
-
-
-
+ // TODO: Adjust actions based on user permissions
+
);
return actions;
diff --git a/src/frontend/src/components/tables/settings/ProjectCodeTable.tsx b/src/frontend/src/components/tables/settings/ProjectCodeTable.tsx
index 794303f7c0..7715b938b8 100644
--- a/src/frontend/src/components/tables/settings/ProjectCodeTable.tsx
+++ b/src/frontend/src/components/tables/settings/ProjectCodeTable.tsx
@@ -1,6 +1,5 @@
import { t } from '@lingui/macro';
-import { ActionIcon, Text, Tooltip } from '@mantine/core';
-import { IconCirclePlus } from '@tabler/icons-react';
+import { Text } from '@mantine/core';
import { useCallback, useMemo } from 'react';
import {
@@ -10,6 +9,7 @@ import {
} from '../../../functions/forms';
import { useTableRefresh } from '../../../hooks/TableRefresh';
import { ApiPaths, apiUrl } from '../../../states/ApiState';
+import { AddItemButton } from '../../buttons/AddItemButton';
import { TableColumn } from '../Column';
import { InvenTreeTable } from '../InvenTreeTable';
import { RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
@@ -86,11 +86,7 @@ export function ProjectCodeTable() {
let actions = [];
actions.push(
-
-
-
-
-
+
);
return actions;
diff --git a/src/frontend/src/components/tables/stock/StockItemTable.tsx b/src/frontend/src/components/tables/stock/StockItemTable.tsx
index 951391bef5..397d40a480 100644
--- a/src/frontend/src/components/tables/stock/StockItemTable.tsx
+++ b/src/frontend/src/components/tables/stock/StockItemTable.tsx
@@ -1,5 +1,5 @@
import { t } from '@lingui/macro';
-import { Group, Stack, Text } from '@mantine/core';
+import { Group, Text } from '@mantine/core';
import { ReactNode, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
@@ -40,7 +40,7 @@ function stockItemTableColumns(): TableColumn[] {
{
accessor: 'part_detail.description',
sortable: false,
- switchable: true,
+
title: t`Description`
},
{
@@ -145,7 +145,7 @@ function stockItemTableColumns(): TableColumn[] {
}
title={t`Stock Information`}
- extra={extra.length > 0 && {extra}}
+ extra={extra}
/>
);
}
@@ -153,7 +153,7 @@ function stockItemTableColumns(): TableColumn[] {
{
accessor: 'status',
sortable: true,
- switchable: true,
+
filter: true,
title: t`Status`,
render: TableStatusRenderer(ModelType.stockitem)
@@ -161,13 +161,13 @@ function stockItemTableColumns(): TableColumn[] {
{
accessor: 'batch',
sortable: true,
- switchable: true,
+
title: t`Batch`
},
{
accessor: 'location',
sortable: true,
- switchable: true,
+
title: t`Location`,
render: function (record: any) {
// TODO: Custom renderer for location
diff --git a/src/frontend/src/components/tables/stock/StockLocationTable.tsx b/src/frontend/src/components/tables/stock/StockLocationTable.tsx
index 2ea8c562e2..06ee99cd5a 100644
--- a/src/frontend/src/components/tables/stock/StockLocationTable.tsx
+++ b/src/frontend/src/components/tables/stock/StockLocationTable.tsx
@@ -25,39 +25,37 @@ export function StockLocationTable({ params = {} }: { params?: any }) {
},
{
accessor: 'description',
- title: t`Description`,
- switchable: true
+ title: t`Description`
},
{
accessor: 'pathstring',
title: t`Path`,
- sortable: true,
- switchable: true
+ sortable: true
},
{
accessor: 'items',
title: t`Stock Items`,
- switchable: true,
+
sortable: true
},
{
accessor: 'structural',
title: t`Structural`,
- switchable: true,
+
sortable: true,
render: (record: any) =>
},
{
accessor: 'external',
title: t`External`,
- switchable: true,
+
sortable: true,
render: (record: any) =>
},
{
accessor: 'location_type',
title: t`Location Type`,
- switchable: true,
+
sortable: false,
render: (record: any) => record.location_type_detail?.name
}
diff --git a/src/frontend/src/forms/PurchaseOrderForms.tsx b/src/frontend/src/forms/PurchaseOrderForms.tsx
new file mode 100644
index 0000000000..8db69c240e
--- /dev/null
+++ b/src/frontend/src/forms/PurchaseOrderForms.tsx
@@ -0,0 +1,65 @@
+import {
+ IconCalendar,
+ IconCoins,
+ IconCurrencyDollar,
+ IconLink,
+ IconNotes,
+ IconSitemap
+} from '@tabler/icons-react';
+
+import {
+ ApiFormData,
+ ApiFormFieldSet
+} from '../components/forms/fields/ApiFormField';
+
+/*
+ * Construct a set of fields for creating / editing a PurchaseOrderLineItem instance
+ */
+export function purchaseOrderLineItemFields({
+ supplierId
+}: {
+ supplierId?: number;
+}) {
+ let fields: ApiFormFieldSet = {
+ order: {
+ filters: {
+ supplier_detail: true
+ }
+ },
+ part: {
+ filters: {
+ part_detail: true,
+ supplier_detail: true,
+ supplier: supplierId
+ },
+ adjustFilters: (filters: any, _form: ApiFormData) => {
+ // TODO: Filter by the supplier associated with the order
+ return filters;
+ }
+ // TODO: Custom onEdit callback (see purchase_order.js)
+ // TODO: secondary modal (see purchase_order.js)
+ },
+ quantity: {},
+ reference: {},
+ purchase_price: {
+ icon:
+ },
+ purchase_price_currency: {
+ icon:
+ },
+ target_date: {
+ icon:
+ },
+ destination: {
+ icon:
+ },
+ notes: {
+ icon:
+ },
+ link: {
+ icon:
+ }
+ };
+
+ return fields;
+}
diff --git a/src/frontend/src/pages/build/BuildDetail.tsx b/src/frontend/src/pages/build/BuildDetail.tsx
index c58f3d6e7c..d8f6f21f72 100644
--- a/src/frontend/src/pages/build/BuildDetail.tsx
+++ b/src/frontend/src/pages/build/BuildDetail.tsx
@@ -8,7 +8,6 @@ import {
IconEdit,
IconFileTypePdf,
IconInfoCircle,
- IconLink,
IconList,
IconListCheck,
IconNotes,
@@ -16,13 +15,20 @@ import {
IconPrinter,
IconQrcode,
IconSitemap,
- IconTrash,
- IconUnlink
+ IconTrash
} from '@tabler/icons-react';
import { useMemo } from 'react';
import { useParams } from 'react-router-dom';
-import { ActionDropdown } from '../../components/items/ActionDropdown';
+import {
+ ActionDropdown,
+ DeleteItemAction,
+ DuplicateItemAction,
+ EditItemAction,
+ LinkBarcodeAction,
+ UnlinkBarcodeAction,
+ ViewBarcodeAction
+} from '../../components/items/ActionDropdown';
import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
import { ModelType } from '../../components/render/ModelType';
@@ -179,23 +185,13 @@ export default function BuildDetail() {
tooltip={t`Barcode Actions`}
icon={}
actions={[
- {
- icon: ,
- name: t`View`,
- tooltip: t`View part barcode`
- },
- {
- icon: ,
- name: t`Link Barcode`,
- tooltip: t`Link custom barcode to part`,
+ ViewBarcodeAction({}),
+ LinkBarcodeAction({
disabled: build?.barcode_hash
- },
- {
- icon: ,
- name: t`Unlink Barcode`,
- tooltip: t`Unlink custom barcode from part`,
+ }),
+ UnlinkBarcodeAction({
disabled: !build?.barcode_hash
- }
+ })
]}
/>,
}
actions={[
- {
- icon: ,
- name: t`Edit`,
- tooltip: t`Edit build order`
- },
- {
- icon: ,
- name: t`Duplicate`,
- tooltip: t`Duplicate build order`
- },
- {
- icon: ,
- name: t`Delete`,
- tooltip: t`Delete build order`
- }
+ EditItemAction({}),
+ DuplicateItemAction({}),
+ DeleteItemAction({})
]}
/>
];
diff --git a/src/frontend/src/pages/company/CompanyDetail.tsx b/src/frontend/src/pages/company/CompanyDetail.tsx
index b9305f6223..2c15fd536e 100644
--- a/src/frontend/src/pages/company/CompanyDetail.tsx
+++ b/src/frontend/src/pages/company/CompanyDetail.tsx
@@ -4,7 +4,6 @@ import {
IconBuildingFactory2,
IconBuildingWarehouse,
IconDots,
- IconEdit,
IconInfoCircle,
IconMap2,
IconNotes,
@@ -12,7 +11,6 @@ import {
IconPackages,
IconPaperclip,
IconShoppingCart,
- IconTrash,
IconTruckDelivery,
IconTruckReturn,
IconUsersGroup
@@ -20,7 +18,11 @@ import {
import { useMemo } from 'react';
import { useParams } from 'react-router-dom';
-import { ActionDropdown } from '../../components/items/ActionDropdown';
+import {
+ ActionDropdown,
+ DeleteItemAction,
+ EditItemAction
+} from '../../components/items/ActionDropdown';
import { Breadcrumb } from '../../components/nav/BreadcrumbList';
import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup } from '../../components/nav/PanelGroup';
@@ -169,12 +171,9 @@ export default function CompanyDetail(props: CompanyDetailProps) {
tooltip={t`Company Actions`}
icon={}
actions={[
- {
- icon: ,
- name: t`Edit`,
- tooltip: t`Edit company`,
+ EditItemAction({
disabled: !canEdit,
- onClick: () => {
+ callback: () => {
if (company?.pk) {
editCompany({
pk: company?.pk,
@@ -182,13 +181,10 @@ export default function CompanyDetail(props: CompanyDetailProps) {
});
}
}
- },
- {
- icon: ,
- name: t`Delete`,
- tooltip: t`Delete company`,
+ }),
+ DeleteItemAction({
disabled: !canDelete
- }
+ })
]}
/>
];
diff --git a/src/frontend/src/pages/part/PartDetail.tsx b/src/frontend/src/pages/part/PartDetail.tsx
index 0c37ac0b1c..bc099996a5 100644
--- a/src/frontend/src/pages/part/PartDetail.tsx
+++ b/src/frontend/src/pages/part/PartDetail.tsx
@@ -6,27 +6,21 @@ import {
IconBuildingFactory2,
IconCalendarStats,
IconClipboardList,
- IconCopy,
IconCurrencyDollar,
IconDots,
- IconEdit,
IconInfoCircle,
IconLayersLinked,
- IconLink,
IconList,
IconListTree,
IconNotes,
IconPackages,
IconPaperclip,
- IconQrcode,
IconShoppingCart,
IconStack2,
IconTestPipe,
IconTools,
IconTransfer,
- IconTrash,
IconTruckDelivery,
- IconUnlink,
IconVersions
} from '@tabler/icons-react';
import { useMemo, useState } from 'react';
@@ -34,7 +28,13 @@ import { useParams } from 'react-router-dom';
import {
ActionDropdown,
- BarcodeActionDropdown
+ BarcodeActionDropdown,
+ DeleteItemAction,
+ DuplicateItemAction,
+ EditItemAction,
+ LinkBarcodeAction,
+ UnlinkBarcodeAction,
+ ViewBarcodeAction
} from '../../components/items/ActionDropdown';
import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
@@ -263,23 +263,13 @@ export default function PartDetail() {
return [
,
- name: t`View`,
- tooltip: t`View part barcode`
- },
- {
- icon: ,
- name: t`Link Barcode`,
- tooltip: t`Link custom barcode to part`,
+ ViewBarcodeAction({}),
+ LinkBarcodeAction({
disabled: part?.barcode_hash
- },
- {
- icon: ,
- name: t`Unlink Barcode`,
- tooltip: t`Unlink custom barcode from part`,
+ }),
+ UnlinkBarcodeAction({
disabled: !part?.barcode_hash
- }
+ })
]}
/>,
}
actions={[
- {
- icon: ,
- name: t`Edit`,
- tooltip: t`Edit part`,
- onClick: () => {
+ DuplicateItemAction({}),
+ EditItemAction({
+ callback: () => {
part.pk &&
editPart({
part_id: part.pk,
callback: refreshInstance
});
}
- },
- {
- icon: ,
- name: t`Duplicate`,
- tooltip: t`Duplicate part`
- },
- {
- icon: ,
- name: t`Delete`,
- tooltip: t`Delete part`
- }
+ }),
+ DeleteItemAction({
+ disabled: part?.active
+ })
]}
/>
];
diff --git a/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx b/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx
index 1fba6fc560..d6fbc6dca5 100644
--- a/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx
+++ b/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx
@@ -1,6 +1,7 @@
import { t } from '@lingui/macro';
import { LoadingOverlay, Stack } from '@mantine/core';
import {
+ IconDots,
IconInfoCircle,
IconList,
IconNotes,
@@ -10,13 +11,24 @@ import {
import { useMemo } from 'react';
import { useParams } from 'react-router-dom';
+import {
+ ActionDropdown,
+ BarcodeActionDropdown,
+ DeleteItemAction,
+ EditItemAction,
+ LinkBarcodeAction,
+ UnlinkBarcodeAction,
+ ViewBarcodeAction
+} from '../../components/items/ActionDropdown';
import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
import { AttachmentTable } from '../../components/tables/general/AttachmentTable';
+import { PurchaseOrderLineItemTable } from '../../components/tables/purchasing/PurchaseOrderLineItemTable';
import { StockItemTable } from '../../components/tables/stock/StockItemTable';
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
import { useInstance } from '../../hooks/UseInstance';
import { ApiPaths, apiUrl } from '../../states/ApiState';
+import { useUserState } from '../../states/UserState';
/**
* Detail page for a single PurchaseOrder
@@ -24,6 +36,8 @@ import { ApiPaths, apiUrl } from '../../states/ApiState';
export default function PurchaseOrderDetail() {
const { id } = useParams();
+ const user = useUserState();
+
const { instance: order, instanceQuery } = useInstance({
endpoint: ApiPaths.purchase_order_list,
pk: id,
@@ -43,7 +57,8 @@ export default function PurchaseOrderDetail() {
{
name: 'line-items',
label: t`Line Items`,
- icon:
+ icon: ,
+ content: order?.pk &&
},
{
name: 'received-stock',
@@ -84,6 +99,29 @@ export default function PurchaseOrderDetail() {
];
}, [order, id]);
+ const poActions = useMemo(() => {
+ // TODO: Disable certain actions based on user permissions
+ return [
+ ,
+ }
+ actions={[EditItemAction({}), DeleteItemAction({})]}
+ />
+ ];
+ }, [id, order, user]);
+
return (
<>
@@ -93,6 +131,7 @@ export default function PurchaseOrderDetail() {
subtitle={order.description}
imageUrl={order.supplier_detail?.image}
breadcrumbs={[{ name: t`Purchasing`, url: '/purchasing/' }]}
+ actions={poActions}
/>
diff --git a/src/frontend/src/pages/stock/StockDetail.tsx b/src/frontend/src/pages/stock/StockDetail.tsx
index 0b62e26f69..f7f0efd188 100644
--- a/src/frontend/src/pages/stock/StockDetail.tsx
+++ b/src/frontend/src/pages/stock/StockDetail.tsx
@@ -9,25 +9,25 @@ import {
IconCirclePlus,
IconCopy,
IconDots,
- IconEdit,
IconHistory,
IconInfoCircle,
- IconLink,
IconNotes,
IconPackages,
IconPaperclip,
- IconQrcode,
IconSitemap,
- IconTransfer,
- IconTrash,
- IconUnlink
+ IconTransfer
} from '@tabler/icons-react';
import { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import {
ActionDropdown,
- BarcodeActionDropdown
+ BarcodeActionDropdown,
+ DeleteItemAction,
+ EditItemAction,
+ LinkBarcodeAction,
+ UnlinkBarcodeAction,
+ ViewBarcodeAction
} from '../../components/items/ActionDropdown';
import { PlaceholderPanel } from '../../components/items/Placeholder';
import { PageDetail } from '../../components/nav/PageDetail';
@@ -144,23 +144,13 @@ export default function StockDetail() {
() => /* TODO: Disable actions based on user permissions*/ [
,
- name: t`View`,
- tooltip: t`View part barcode`
- },
- {
- icon: ,
- name: t`Link Barcode`,
- tooltip: t`Link custom barcode to stock item`,
+ ViewBarcodeAction({}),
+ LinkBarcodeAction({
disabled: stockitem?.barcode_hash
- },
- {
- icon: ,
- name: t`Unlink Barcode`,
- tooltip: t`Unlink custom barcode from stock item`,
+ }),
+ UnlinkBarcodeAction({
disabled: !stockitem?.barcode_hash
- }
+ })
]}
/>,
},
- {
- name: t`Edit`,
- tooltip: t`Edit stock item`,
- icon: ,
- onClick: () => {
+ EditItemAction({
+ callback: () => {
stockitem.pk &&
editStockItem({
item_id: stockitem.pk,
callback: () => refreshInstance
});
}
- },
- {
- name: t`Delete`,
- tooltip: t`Delete stock item`,
- icon:
- }
+ }),
+ DeleteItemAction({})
]}
/>
],
diff --git a/src/frontend/src/states/ApiState.tsx b/src/frontend/src/states/ApiState.tsx
index 8b54433277..2aff116728 100644
--- a/src/frontend/src/states/ApiState.tsx
+++ b/src/frontend/src/states/ApiState.tsx
@@ -106,6 +106,7 @@ export enum ApiPaths {
// Purchase Order URLs
purchase_order_list = 'api-purchase-order-list',
+ purchase_order_line_list = 'api-purchase-order-line-list',
purchase_order_attachment_list = 'api-purchase-order-attachment-list',
// Sales Order URLs
@@ -218,6 +219,8 @@ export function apiEndpoint(path: ApiPaths): string {
return 'stock/attachment/';
case ApiPaths.purchase_order_list:
return 'order/po/';
+ case ApiPaths.purchase_order_line_list:
+ return 'order/po-line/';
case ApiPaths.purchase_order_attachment_list:
return 'order/po/attachment/';
case ApiPaths.sales_order_list: