mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-30 20:46:47 +00:00
[React] PO detail pgae (#5847)
* Factor out common barcode actions * Refactoring more icons * Add PurchaseOrderLineItemTable component * Improve renderer for SupplierPart * Edit line item * Table action column always visible * Create <AddItemButton> component (refactoring) * Table updates - Improve actions column for table - Move "download" button to right hand side * Refactoring button components * More cleanup - Refactor <TableHoverCard> a bit - Add placeholder for "receive items" * Add ProgresBar component * Make table columns switchable by default - set switchable: false to disable * Add project_code column to build table * Fix row actions column for tables without actions * Improve rendering for BuildOrderTable * Cleanup unused imports * Further fixes * Remove another unused import
This commit is contained in:
parent
19810d0965
commit
361fc097a7
44
src/frontend/src/components/buttons/ActionButton.tsx
Normal file
44
src/frontend/src/components/buttons/ActionButton.tsx
Normal file
@ -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 && (
|
||||||
|
<Tooltip
|
||||||
|
key={props.text ?? props.tooltip}
|
||||||
|
disabled={!props.tooltip && !props.text}
|
||||||
|
label={props.tooltip ?? props.text}
|
||||||
|
position="left"
|
||||||
|
>
|
||||||
|
<ActionIcon
|
||||||
|
disabled={props.disabled}
|
||||||
|
radius="xs"
|
||||||
|
color={props.color}
|
||||||
|
size={props.size}
|
||||||
|
onClick={props.onClick ?? notYetImplemented}
|
||||||
|
>
|
||||||
|
<Group spacing="xs" noWrap={true}>
|
||||||
|
{props.icon}
|
||||||
|
</Group>
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
10
src/frontend/src/components/buttons/AddItemButton.tsx
Normal file
10
src/frontend/src/components/buttons/AddItemButton.tsx
Normal file
@ -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 <ActionButton {...props} color="green" icon={<IconPlus />} />;
|
||||||
|
}
|
@ -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 (
|
|
||||||
<ActionIcon
|
|
||||||
disabled={disabled}
|
|
||||||
radius="xs"
|
|
||||||
color={color}
|
|
||||||
size={size}
|
|
||||||
onClick={onClick}
|
|
||||||
>
|
|
||||||
<Tooltip disabled={!tooltip} label={tooltip} position="left">
|
|
||||||
{icon}
|
|
||||||
</Tooltip>
|
|
||||||
</ActionIcon>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,6 +1,13 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { ActionIcon, Menu, Tooltip } from '@mantine/core';
|
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 { ReactNode, useMemo } from 'react';
|
||||||
|
|
||||||
import { notYetImplemented } from '../../functions/notifications';
|
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: <IconQrcode />,
|
||||||
|
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: <IconLink />,
|
||||||
|
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: <IconUnlink />,
|
||||||
|
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: <IconEdit color="blue" />,
|
||||||
|
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: <IconTrash color="red" />,
|
||||||
|
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: <IconCopy color="green" />,
|
||||||
|
name: t`Duplicate`,
|
||||||
|
tooltip: tooltip ?? t`Duplicate item`,
|
||||||
|
onClick: callback,
|
||||||
|
disabled: disabled
|
||||||
|
};
|
||||||
|
}
|
||||||
|
39
src/frontend/src/components/items/ProgressBar.tsx
Normal file
39
src/frontend/src/components/items/ProgressBar.tsx
Normal file
@ -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 (
|
||||||
|
<Stack spacing={2}>
|
||||||
|
{props.progressLabel && (
|
||||||
|
<Text align="center" size="xs">
|
||||||
|
{props.value} / {props.maximum}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
<Progress
|
||||||
|
value={progress}
|
||||||
|
color={progress < 100 ? 'orange' : progress > 100 ? 'blue' : 'green'}
|
||||||
|
size="sm"
|
||||||
|
radius="xs"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
@ -52,19 +52,19 @@ export function RenderContact({ instance }: { instance: any }): ReactNode {
|
|||||||
* Inline rendering of a single SupplierPart instance
|
* Inline rendering of a single SupplierPart instance
|
||||||
*/
|
*/
|
||||||
export function RenderSupplierPart({ instance }: { instance: any }): ReactNode {
|
export function RenderSupplierPart({ instance }: { instance: any }): ReactNode {
|
||||||
// TODO: Handle image
|
|
||||||
// TODO: handle URL
|
// TODO: handle URL
|
||||||
|
|
||||||
let supplier = instance.supplier_detail ?? {};
|
let supplier = instance.supplier_detail ?? {};
|
||||||
let part = instance.part_detail ?? {};
|
let part = instance.part_detail ?? {};
|
||||||
|
|
||||||
let text = instance.SKU;
|
return (
|
||||||
|
<RenderInlineModel
|
||||||
if (supplier.name) {
|
primary={supplier?.name}
|
||||||
text = `${supplier.name} | ${text}`;
|
secondary={instance.SKU}
|
||||||
}
|
image={part?.thumbnail ?? part?.image}
|
||||||
|
suffix={part.full_name}
|
||||||
return <RenderInlineModel primary={text} secondary={part.full_name} />;
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -5,14 +5,16 @@ import { RenderInlineModel } from './Instance';
|
|||||||
export function RenderOwner({ instance }: { instance: any }): ReactNode {
|
export function RenderOwner({ instance }: { instance: any }): ReactNode {
|
||||||
// TODO: Icon based on user / group status?
|
// TODO: Icon based on user / group status?
|
||||||
|
|
||||||
return <RenderInlineModel primary={instance.name} />;
|
return instance && <RenderInlineModel primary={instance.name} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RenderUser({ instance }: { instance: any }): ReactNode {
|
export function RenderUser({ instance }: { instance: any }): ReactNode {
|
||||||
return (
|
return (
|
||||||
|
instance && (
|
||||||
<RenderInlineModel
|
<RenderInlineModel
|
||||||
primary={instance.username}
|
primary={instance.username}
|
||||||
secondary={`${instance.first_name} ${instance.last_name}`}
|
secondary={`${instance.first_name} ${instance.last_name}`}
|
||||||
/>
|
/>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ export const PurchaseOrderRenderer = ({ pk }: { pk: string }) => {
|
|||||||
return (
|
return (
|
||||||
<GeneralRenderer
|
<GeneralRenderer
|
||||||
api_key={ApiPaths.purchase_order_list}
|
api_key={ApiPaths.purchase_order_list}
|
||||||
api_ref="pruchaseorder"
|
api_ref="purchaseorder"
|
||||||
link={`/order/purchase-order/${pk}`}
|
link={`/order/purchase-order/${pk}`}
|
||||||
pk={pk}
|
pk={pk}
|
||||||
renderer={DetailRenderer}
|
renderer={DetailRenderer}
|
||||||
|
@ -15,4 +15,5 @@ export type TableColumn = {
|
|||||||
noWrap?: boolean; // Whether the column should wrap
|
noWrap?: boolean; // Whether the column should wrap
|
||||||
ellipsis?: boolean; // Whether the column should be ellipsized
|
ellipsis?: boolean; // Whether the column should be ellipsized
|
||||||
textAlignment?: 'left' | 'center' | 'right'; // The text alignment of the column
|
textAlignment?: 'left' | 'center' | 'right'; // The text alignment of the column
|
||||||
|
cellsStyle?: any; // The style of the cells in the column
|
||||||
};
|
};
|
||||||
|
@ -23,7 +23,7 @@ export function TableColumnSelect({
|
|||||||
<Menu.Dropdown>
|
<Menu.Dropdown>
|
||||||
<Menu.Label>{t`Select Columns`}</Menu.Label>
|
<Menu.Label>{t`Select Columns`}</Menu.Label>
|
||||||
{columns
|
{columns
|
||||||
.filter((col) => col.switchable)
|
.filter((col) => col.switchable ?? true)
|
||||||
.map((col) => (
|
.map((col) => (
|
||||||
<Menu.Item key={col.accessor}>
|
<Menu.Item key={col.accessor}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
@ -9,7 +9,7 @@ import { DataTable, DataTableSortStatus } from 'mantine-datatable';
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { api } from '../../App';
|
import { api } from '../../App';
|
||||||
import { ButtonMenu } from '../items/ButtonMenu';
|
import { ButtonMenu } from '../buttons/ButtonMenu';
|
||||||
import { TableColumn } from './Column';
|
import { TableColumn } from './Column';
|
||||||
import { TableColumnSelect } from './ColumnSelect';
|
import { TableColumnSelect } from './ColumnSelect';
|
||||||
import { DownloadAction } from './DownloadAction';
|
import { DownloadAction } from './DownloadAction';
|
||||||
@ -82,7 +82,6 @@ const defaultInvenTreeTableProps: InvenTreeTableProps = {
|
|||||||
customFilters: [],
|
customFilters: [],
|
||||||
customActionGroups: [],
|
customActionGroups: [],
|
||||||
idAccessor: 'pk',
|
idAccessor: 'pk',
|
||||||
rowActions: (record: any) => [],
|
|
||||||
onRowClick: (record: any, index: number, event: any) => {}
|
onRowClick: (record: any, index: number, event: any) => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -115,7 +114,7 @@ export function InvenTreeTable({
|
|||||||
|
|
||||||
// Check if any columns are switchable (can be hidden)
|
// Check if any columns are switchable (can be hidden)
|
||||||
const hasSwitchableColumns = columns.some(
|
const hasSwitchableColumns = columns.some(
|
||||||
(col: TableColumn) => col.switchable
|
(col: TableColumn) => col.switchable ?? true
|
||||||
);
|
);
|
||||||
|
|
||||||
// A list of hidden columns, saved to local storage
|
// A list of hidden columns, saved to local storage
|
||||||
@ -142,7 +141,7 @@ export function InvenTreeTable({
|
|||||||
let cols = columns.map((col) => {
|
let cols = columns.map((col) => {
|
||||||
let hidden: boolean = col.hidden ?? false;
|
let hidden: boolean = col.hidden ?? false;
|
||||||
|
|
||||||
if (col.switchable) {
|
if (col.switchable ?? true) {
|
||||||
hidden = hiddenColumns.includes(col.accessor);
|
hidden = hiddenColumns.includes(col.accessor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,10 +155,19 @@ export function InvenTreeTable({
|
|||||||
if (tableProps.rowActions) {
|
if (tableProps.rowActions) {
|
||||||
cols.push({
|
cols.push({
|
||||||
accessor: 'actions',
|
accessor: 'actions',
|
||||||
title: '',
|
title: ' ',
|
||||||
hidden: false,
|
hidden: false,
|
||||||
switchable: 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) {
|
render: function (record: any) {
|
||||||
return (
|
return (
|
||||||
<RowActions
|
<RowActions
|
||||||
@ -445,12 +453,6 @@ export function InvenTreeTable({
|
|||||||
actions={tableProps.printingActions ?? []}
|
actions={tableProps.printingActions ?? []}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{tableProps.enableDownload && (
|
|
||||||
<DownloadAction
|
|
||||||
key="download-action"
|
|
||||||
downloadCallback={downloadData}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Group>
|
</Group>
|
||||||
<Space />
|
<Space />
|
||||||
<Group position="right" spacing={5}>
|
<Group position="right" spacing={5}>
|
||||||
@ -488,6 +490,12 @@ export function InvenTreeTable({
|
|||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Indicator>
|
</Indicator>
|
||||||
)}
|
)}
|
||||||
|
{tableProps.enableDownload && (
|
||||||
|
<DownloadAction
|
||||||
|
key="download-action"
|
||||||
|
downloadCallback={downloadData}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
{filtersVisible && (
|
{filtersVisible && (
|
||||||
@ -504,7 +512,7 @@ export function InvenTreeTable({
|
|||||||
highlightOnHover
|
highlightOnHover
|
||||||
loaderVariant="dots"
|
loaderVariant="dots"
|
||||||
idAccessor={tableProps.idAccessor}
|
idAccessor={tableProps.idAccessor}
|
||||||
minHeight={200}
|
minHeight={300}
|
||||||
totalRecords={recordCount}
|
totalRecords={recordCount}
|
||||||
recordsPerPage={tableProps.pageSize ?? defaultPageSize}
|
recordsPerPage={tableProps.pageSize ?? defaultPageSize}
|
||||||
page={page}
|
page={page}
|
||||||
|
@ -15,7 +15,23 @@ export type RowAction = {
|
|||||||
hidden?: boolean;
|
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({
|
export function RowEditAction({
|
||||||
onClick,
|
onClick,
|
||||||
hidden
|
hidden
|
||||||
@ -25,7 +41,7 @@ export function RowEditAction({
|
|||||||
}): RowAction {
|
}): RowAction {
|
||||||
return {
|
return {
|
||||||
title: t`Edit`,
|
title: t`Edit`,
|
||||||
color: 'green',
|
color: 'blue',
|
||||||
onClick: onClick,
|
onClick: onClick,
|
||||||
hidden: hidden
|
hidden: hidden
|
||||||
};
|
};
|
||||||
|
@ -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 { IconInfoCircle } from '@tabler/icons-react';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A custom hovercard element for displaying extra information in a table cell.
|
* 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
|
title // The title of the hovercard
|
||||||
}: {
|
}: {
|
||||||
value: any;
|
value: any;
|
||||||
extra?: any;
|
extra?: ReactNode;
|
||||||
title?: string;
|
title?: string;
|
||||||
}) {
|
}) {
|
||||||
// If no extra information presented, just return the raw value
|
// If no extra information presented, just return the raw value
|
||||||
@ -32,7 +33,7 @@ export function TableHoverCard({
|
|||||||
<Stack spacing="xs">
|
<Stack spacing="xs">
|
||||||
<Group spacing="xs" position="left">
|
<Group spacing="xs" position="left">
|
||||||
<IconInfoCircle size="16" color="blue" />
|
<IconInfoCircle size="16" color="blue" />
|
||||||
{title}
|
<Text weight="bold">{title}</Text>
|
||||||
</Group>
|
</Group>
|
||||||
<Divider />
|
<Divider />
|
||||||
{extra}
|
{extra}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Stack, Text } from '@mantine/core';
|
import { Text } from '@mantine/core';
|
||||||
import { ReactNode, useCallback, useMemo } from 'react';
|
import { ReactNode, useCallback, useMemo } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
@ -51,12 +51,11 @@ export function BomTable({
|
|||||||
{
|
{
|
||||||
accessor: 'description',
|
accessor: 'description',
|
||||||
title: t`Description`,
|
title: t`Description`,
|
||||||
switchable: true,
|
|
||||||
render: (row) => row?.sub_part_detail?.description
|
render: (row) => row?.sub_part_detail?.description
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'reference',
|
accessor: 'reference',
|
||||||
switchable: true,
|
|
||||||
title: t`Reference`
|
title: t`Reference`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -66,7 +65,7 @@ export function BomTable({
|
|||||||
{
|
{
|
||||||
accessor: 'substitutes',
|
accessor: 'substitutes',
|
||||||
title: t`Substitutes`,
|
title: t`Substitutes`,
|
||||||
switchable: true,
|
|
||||||
render: (row) => {
|
render: (row) => {
|
||||||
let substitutes = row.substitutes ?? [];
|
let substitutes = row.substitutes ?? [];
|
||||||
|
|
||||||
@ -80,7 +79,7 @@ export function BomTable({
|
|||||||
{
|
{
|
||||||
accessor: 'optional',
|
accessor: 'optional',
|
||||||
title: t`Optional`,
|
title: t`Optional`,
|
||||||
switchable: true,
|
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (row) => {
|
render: (row) => {
|
||||||
return <YesNoButton value={row.optional} />;
|
return <YesNoButton value={row.optional} />;
|
||||||
@ -89,7 +88,7 @@ export function BomTable({
|
|||||||
{
|
{
|
||||||
accessor: 'consumable',
|
accessor: 'consumable',
|
||||||
title: t`Consumable`,
|
title: t`Consumable`,
|
||||||
switchable: true,
|
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (row) => {
|
render: (row) => {
|
||||||
return <YesNoButton value={row.consumable} />;
|
return <YesNoButton value={row.consumable} />;
|
||||||
@ -98,7 +97,7 @@ export function BomTable({
|
|||||||
{
|
{
|
||||||
accessor: 'allow_variants',
|
accessor: 'allow_variants',
|
||||||
title: t`Allow Variants`,
|
title: t`Allow Variants`,
|
||||||
switchable: true,
|
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (row) => {
|
render: (row) => {
|
||||||
return <YesNoButton value={row.allow_variants} />;
|
return <YesNoButton value={row.allow_variants} />;
|
||||||
@ -107,7 +106,7 @@ export function BomTable({
|
|||||||
{
|
{
|
||||||
accessor: 'inherited',
|
accessor: 'inherited',
|
||||||
title: t`Gets Inherited`,
|
title: t`Gets Inherited`,
|
||||||
switchable: true,
|
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (row) => {
|
render: (row) => {
|
||||||
// TODO: Update complexity here
|
// TODO: Update complexity here
|
||||||
@ -117,7 +116,7 @@ export function BomTable({
|
|||||||
{
|
{
|
||||||
accessor: 'price_range',
|
accessor: 'price_range',
|
||||||
title: t`Price Range`,
|
title: t`Price Range`,
|
||||||
switchable: true,
|
|
||||||
sortable: false,
|
sortable: false,
|
||||||
render: (row) => {
|
render: (row) => {
|
||||||
let min_price = row.pricing_min || row.pricing_max;
|
let min_price = row.pricing_min || row.pricing_max;
|
||||||
@ -130,7 +129,7 @@ export function BomTable({
|
|||||||
{
|
{
|
||||||
accessor: 'available_stock',
|
accessor: 'available_stock',
|
||||||
title: t`Available`,
|
title: t`Available`,
|
||||||
switchable: true,
|
|
||||||
render: (row) => {
|
render: (row) => {
|
||||||
let extra: ReactNode[] = [];
|
let extra: ReactNode[] = [];
|
||||||
|
|
||||||
@ -164,9 +163,7 @@ export function BomTable({
|
|||||||
return (
|
return (
|
||||||
<TableHoverCard
|
<TableHoverCard
|
||||||
value={available_stock}
|
value={available_stock}
|
||||||
extra={
|
extra={extra}
|
||||||
extra.length > 0 ? <Stack spacing="xs">{extra}</Stack> : null
|
|
||||||
}
|
|
||||||
title={t`Available Stock`}
|
title={t`Available Stock`}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -175,7 +172,7 @@ export function BomTable({
|
|||||||
{
|
{
|
||||||
accessor: 'can_build',
|
accessor: 'can_build',
|
||||||
title: t`Can Build`,
|
title: t`Can Build`,
|
||||||
switchable: true,
|
|
||||||
sortable: true // TODO: Custom sorting via API
|
sortable: true // TODO: Custom sorting via API
|
||||||
// TODO: Reference bom.js for canBuildQuantity method
|
// TODO: Reference bom.js for canBuildQuantity method
|
||||||
},
|
},
|
||||||
|
@ -73,8 +73,7 @@ export function UsedInTable({
|
|||||||
{
|
{
|
||||||
accessor: 'reference',
|
accessor: 'reference',
|
||||||
title: t`Reference`,
|
title: t`Reference`,
|
||||||
sortable: true,
|
sortable: true
|
||||||
switchable: true
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}, [partId]);
|
}, [partId]);
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Progress } from '@mantine/core';
|
import { Text } from '@mantine/core';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||||
import { ThumbnailHoverCard } from '../../images/Thumbnail';
|
import { ThumbnailHoverCard } from '../../images/Thumbnail';
|
||||||
|
import { ProgressBar } from '../../items/ProgressBar';
|
||||||
import { ModelType } from '../../render/ModelType';
|
import { ModelType } from '../../render/ModelType';
|
||||||
|
import { RenderOwner, RenderUser } from '../../render/User';
|
||||||
import { TableStatusRenderer } from '../../renderers/StatusRenderer';
|
import { TableStatusRenderer } from '../../renderers/StatusRenderer';
|
||||||
import { TableColumn } from '../Column';
|
import { TableColumn } from '../Column';
|
||||||
import { TableFilter } from '../Filter';
|
import { TableFilter } from '../Filter';
|
||||||
import { InvenTreeTable } from '../InvenTreeTable';
|
import { InvenTreeTable } from '../InvenTreeTable';
|
||||||
|
import { TableHoverCard } from '../TableHoverCard';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a list of columns for the build order table
|
* Construct a list of columns for the build order table
|
||||||
@ -20,12 +23,13 @@ function buildOrderTableColumns(): TableColumn[] {
|
|||||||
{
|
{
|
||||||
accessor: 'reference',
|
accessor: 'reference',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
|
switchable: false,
|
||||||
title: t`Reference`
|
title: t`Reference`
|
||||||
// TODO: Link to the build order detail page
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'part',
|
accessor: 'part',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
|
switchable: false,
|
||||||
title: t`Part`,
|
title: t`Part`,
|
||||||
render: (record: any) => {
|
render: (record: any) => {
|
||||||
let part = record.part_detail;
|
let part = record.part_detail;
|
||||||
@ -44,86 +48,87 @@ function buildOrderTableColumns(): TableColumn[] {
|
|||||||
{
|
{
|
||||||
accessor: 'title',
|
accessor: 'title',
|
||||||
sortable: false,
|
sortable: false,
|
||||||
title: t`Description`,
|
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
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'quantity',
|
accessor: 'quantity',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
title: t`Quantity`,
|
title: t`Quantity`,
|
||||||
switchable: true
|
switchable: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'completed',
|
accessor: 'completed',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
title: t`Completed`,
|
title: t`Completed`,
|
||||||
render: (record: any) => {
|
render: (record: any) => (
|
||||||
let progress =
|
<ProgressBar
|
||||||
record.quantity <= 0 ? 0 : (100 * record.completed) / record.quantity;
|
progressLabel={true}
|
||||||
return (
|
value={record.completed}
|
||||||
<Progress
|
maximum={record.quantity}
|
||||||
value={progress}
|
|
||||||
label={record.completed}
|
|
||||||
color={progress < 100 ? 'blue' : 'green'}
|
|
||||||
size="xl"
|
|
||||||
radius="xl"
|
|
||||||
/>
|
/>
|
||||||
);
|
)
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'status',
|
accessor: 'status',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
title: t`Status`,
|
title: t`Status`,
|
||||||
switchable: true,
|
|
||||||
render: TableStatusRenderer(ModelType.build)
|
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 ? (
|
||||||
|
<TableHoverCard
|
||||||
|
value={project.code}
|
||||||
|
title={t`Project Code`}
|
||||||
|
extra={<Text>{project.description}</Text>}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
'-'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
accessor: 'priority',
|
accessor: 'priority',
|
||||||
title: t`Priority`,
|
title: t`Priority`,
|
||||||
sortable: true,
|
sortable: true
|
||||||
switchable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'creation_date',
|
accessor: 'creation_date',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
title: t`Created`,
|
title: t`Created`
|
||||||
switchable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'target_date',
|
accessor: 'target_date',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
title: t`Target Date`,
|
title: t`Target Date`
|
||||||
switchable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'completion_date',
|
accessor: 'completion_date',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
title: t`Completed`,
|
title: t`Completed`
|
||||||
switchable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'issued_by',
|
accessor: 'issued_by',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
title: t`Issued By`,
|
title: t`Issued By`,
|
||||||
switchable: true
|
render: (record: any) => (
|
||||||
// TODO: custom render function
|
<RenderUser instance={record?.issued_by_detail} />
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'responsible',
|
accessor: 'responsible',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
title: t`Responsible`,
|
title: t`Responsible`,
|
||||||
switchable: true
|
render: (record: any) => (
|
||||||
// TODO: custom render function
|
<RenderOwner instance={record?.responsible_detail} />
|
||||||
|
)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ function attachmentTableColumns(): TableColumn[] {
|
|||||||
accessor: 'comment',
|
accessor: 'comment',
|
||||||
title: t`Comment`,
|
title: t`Comment`,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
switchable: true,
|
|
||||||
render: function (record: any) {
|
render: function (record: any) {
|
||||||
return record.comment;
|
return record.comment;
|
||||||
}
|
}
|
||||||
@ -54,7 +54,7 @@ function attachmentTableColumns(): TableColumn[] {
|
|||||||
accessor: 'uploaded',
|
accessor: 'uploaded',
|
||||||
title: t`Uploaded`,
|
title: t`Uploaded`,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
switchable: true,
|
|
||||||
render: function (record: any) {
|
render: function (record: any) {
|
||||||
return (
|
return (
|
||||||
<Group position="apart">
|
<Group position="apart">
|
||||||
|
@ -45,14 +45,12 @@ export function CompanyTable({
|
|||||||
{
|
{
|
||||||
accessor: 'description',
|
accessor: 'description',
|
||||||
title: t`Description`,
|
title: t`Description`,
|
||||||
sortable: false,
|
sortable: false
|
||||||
switchable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'website',
|
accessor: 'website',
|
||||||
title: t`Website`,
|
title: t`Website`,
|
||||||
sortable: false,
|
sortable: false
|
||||||
switchable: true
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -26,20 +26,17 @@ export function PartCategoryTable({ params = {} }: { params?: any }) {
|
|||||||
{
|
{
|
||||||
accessor: 'description',
|
accessor: 'description',
|
||||||
title: t`Description`,
|
title: t`Description`,
|
||||||
sortable: false,
|
sortable: false
|
||||||
switchable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'pathstring',
|
accessor: 'pathstring',
|
||||||
title: t`Path`,
|
title: t`Path`,
|
||||||
sortable: false,
|
sortable: false
|
||||||
switchable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'part_count',
|
accessor: 'part_count',
|
||||||
title: t`Parts`,
|
title: t`Parts`,
|
||||||
sortable: true,
|
sortable: true
|
||||||
switchable: true
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { ActionIcon, Group, Text, Tooltip } from '@mantine/core';
|
import { Group, Text } from '@mantine/core';
|
||||||
import { IconTextPlus } from '@tabler/icons-react';
|
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -10,6 +9,7 @@ import {
|
|||||||
} from '../../../functions/forms';
|
} from '../../../functions/forms';
|
||||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||||
|
import { AddItemButton } from '../../buttons/AddItemButton';
|
||||||
import { Thumbnail } from '../../images/Thumbnail';
|
import { Thumbnail } from '../../images/Thumbnail';
|
||||||
import { YesNoButton } from '../../items/YesNoButton';
|
import { YesNoButton } from '../../items/YesNoButton';
|
||||||
import { TableColumn } from '../Column';
|
import { TableColumn } from '../Column';
|
||||||
@ -27,7 +27,7 @@ export function PartParameterTable({ partId }: { partId: any }) {
|
|||||||
{
|
{
|
||||||
accessor: 'part',
|
accessor: 'part',
|
||||||
title: t`Part`,
|
title: t`Part`,
|
||||||
switchable: true,
|
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: function (record: any) {
|
render: function (record: any) {
|
||||||
let part = record?.part_detail ?? {};
|
let part = record?.part_detail ?? {};
|
||||||
@ -59,7 +59,7 @@ export function PartParameterTable({ partId }: { partId: any }) {
|
|||||||
accessor: 'description',
|
accessor: 'description',
|
||||||
title: t`Description`,
|
title: t`Description`,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
switchable: true,
|
|
||||||
render: (record) => record.template_detail?.description
|
render: (record) => record.template_detail?.description
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -86,7 +86,7 @@ export function PartParameterTable({ partId }: { partId: any }) {
|
|||||||
{
|
{
|
||||||
accessor: 'units',
|
accessor: 'units',
|
||||||
title: t`Units`,
|
title: t`Units`,
|
||||||
switchable: true,
|
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (record) => record.template_detail?.units
|
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
|
// TODO: Hide if user does not have permission to edit parts
|
||||||
actions.push(
|
actions.push(
|
||||||
<Tooltip label={t`Add parameter`}>
|
<AddItemButton tooltip="Add parameter" onClick={addParameter} />
|
||||||
<ActionIcon radius="sm" onClick={addParameter}>
|
|
||||||
<IconTextPlus color="green" />
|
|
||||||
</ActionIcon>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return actions;
|
return actions;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Group, Stack, Text } from '@mantine/core';
|
import { Group, Text } from '@mantine/core';
|
||||||
import { ReactNode, useMemo } from 'react';
|
import { ReactNode, useMemo } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
@ -35,26 +35,23 @@ function partTableColumns(): TableColumn[] {
|
|||||||
{
|
{
|
||||||
accessor: 'IPN',
|
accessor: 'IPN',
|
||||||
title: t`IPN`,
|
title: t`IPN`,
|
||||||
sortable: true,
|
sortable: true
|
||||||
switchable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'units',
|
accessor: 'units',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
title: t`Units`,
|
title: t`Units`
|
||||||
switchable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'description',
|
accessor: 'description',
|
||||||
title: t`Description`,
|
title: t`Description`,
|
||||||
sortable: true,
|
sortable: true
|
||||||
switchable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'category',
|
accessor: 'category',
|
||||||
title: t`Category`,
|
title: t`Category`,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
switchable: true,
|
|
||||||
render: function (record: any) {
|
render: function (record: any) {
|
||||||
// TODO: Link to the category detail page
|
// TODO: Link to the category detail page
|
||||||
return shortenString({
|
return shortenString({
|
||||||
@ -66,7 +63,7 @@ function partTableColumns(): TableColumn[] {
|
|||||||
accessor: 'total_in_stock',
|
accessor: 'total_in_stock',
|
||||||
title: t`Stock`,
|
title: t`Stock`,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
switchable: true,
|
|
||||||
render: (record) => {
|
render: (record) => {
|
||||||
let extra: ReactNode[] = [];
|
let extra: ReactNode[] = [];
|
||||||
|
|
||||||
@ -143,7 +140,7 @@ function partTableColumns(): TableColumn[] {
|
|||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
title={t`Stock Information`}
|
title={t`Stock Information`}
|
||||||
extra={extra.length > 0 && <Stack spacing="xs">{extra}</Stack>}
|
extra={extra}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -152,7 +149,7 @@ function partTableColumns(): TableColumn[] {
|
|||||||
accessor: 'price_range',
|
accessor: 'price_range',
|
||||||
title: t`Price Range`,
|
title: t`Price Range`,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
switchable: true,
|
|
||||||
render: function (record: any) {
|
render: function (record: any) {
|
||||||
// TODO: Render price range
|
// TODO: Render price range
|
||||||
return '-- price --';
|
return '-- price --';
|
||||||
@ -160,8 +157,7 @@ function partTableColumns(): TableColumn[] {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'link',
|
accessor: 'link',
|
||||||
title: t`Link`,
|
title: t`Link`
|
||||||
switchable: true
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ export function PluginListTable({ props }: { props: InvenTreeTableProps }) {
|
|||||||
accessor: 'meta.description',
|
accessor: 'meta.description',
|
||||||
title: t`Description`,
|
title: t`Description`,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
switchable: true,
|
|
||||||
render: function (record: any) {
|
render: function (record: any) {
|
||||||
if (record.active) {
|
if (record.active) {
|
||||||
return record.meta.description;
|
return record.meta.description;
|
||||||
@ -80,15 +80,14 @@ export function PluginListTable({ props }: { props: InvenTreeTableProps }) {
|
|||||||
{
|
{
|
||||||
accessor: 'meta.version',
|
accessor: 'meta.version',
|
||||||
title: t`Version`,
|
title: t`Version`,
|
||||||
sortable: false,
|
sortable: false
|
||||||
switchable: true
|
|
||||||
// TODO: Display date information if available
|
// TODO: Display date information if available
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'meta.author',
|
accessor: 'meta.author',
|
||||||
title: 'Author',
|
title: 'Author',
|
||||||
sortable: false,
|
sortable: false
|
||||||
switchable: true
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[]
|
[]
|
||||||
|
@ -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 (
|
||||||
|
<Thumbnail
|
||||||
|
text={record?.part_detail?.name}
|
||||||
|
src={record?.part_detail?.thumbnail ?? record?.part_detail?.image}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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(
|
||||||
|
<Text key="pack-quantity">
|
||||||
|
{t`Pack Quantity`}: {supplier_part.pack_quantity}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
|
||||||
|
extra.push(
|
||||||
|
<Text key="total-quantity">
|
||||||
|
{t`Total Quantity`}: {total}
|
||||||
|
{part.units}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableHoverCard
|
||||||
|
value={record.quantity}
|
||||||
|
extra={extra}
|
||||||
|
title={t`Quantity`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: 'received',
|
||||||
|
title: t`Received`,
|
||||||
|
sortable: false,
|
||||||
|
|
||||||
|
render: (record: any) => (
|
||||||
|
<ProgressBar
|
||||||
|
progressLabel={true}
|
||||||
|
value={record.received}
|
||||||
|
maximum={record.quantity}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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 [
|
||||||
|
<AddItemButton
|
||||||
|
tooltip={t`Add line item`}
|
||||||
|
onClick={addLine}
|
||||||
|
hidden={!user?.checkUserRole('purchaseorder', 'add')}
|
||||||
|
/>,
|
||||||
|
<ActionButton text={t`Receive items`} icon={<IconSquareArrowRight />} />
|
||||||
|
];
|
||||||
|
}, [orderId, user]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InvenTreeTable
|
||||||
|
url={apiUrl(ApiPaths.purchase_order_line_list)}
|
||||||
|
tableKey={tableKey}
|
||||||
|
columns={tableColumns}
|
||||||
|
props={{
|
||||||
|
enableSelection: true,
|
||||||
|
enableDownload: true,
|
||||||
|
params: {
|
||||||
|
...params,
|
||||||
|
order: orderId,
|
||||||
|
part_detail: true
|
||||||
|
},
|
||||||
|
rowActions: rowActions,
|
||||||
|
customActionGroups: tableActions
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -33,8 +33,7 @@ export function PurchaseOrderTable({ params }: { params?: any }) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'description',
|
accessor: 'description',
|
||||||
title: t`Description`,
|
title: t`Description`
|
||||||
switchable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'supplier__name',
|
accessor: 'supplier__name',
|
||||||
@ -54,20 +53,19 @@ export function PurchaseOrderTable({ params }: { params?: any }) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'supplier_reference',
|
accessor: 'supplier_reference',
|
||||||
title: t`Supplier Reference`,
|
title: t`Supplier Reference`
|
||||||
switchable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'project_code',
|
accessor: 'project_code',
|
||||||
title: t`Project Code`,
|
title: t`Project Code`
|
||||||
switchable: true
|
|
||||||
// TODO: Custom project code formatter
|
// TODO: Custom project code formatter
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'status',
|
accessor: 'status',
|
||||||
title: t`Status`,
|
title: t`Status`,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
switchable: true,
|
|
||||||
render: (record: any) =>
|
render: (record: any) =>
|
||||||
StatusRenderer({
|
StatusRenderer({
|
||||||
status: record.status,
|
status: record.status,
|
||||||
@ -76,34 +74,33 @@ export function PurchaseOrderTable({ params }: { params?: any }) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'creation_date',
|
accessor: 'creation_date',
|
||||||
title: t`Created`,
|
title: t`Created`
|
||||||
switchable: true
|
|
||||||
// TODO: Custom date formatter
|
// TODO: Custom date formatter
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'target_date',
|
accessor: 'target_date',
|
||||||
title: t`Target Date`,
|
title: t`Target Date`
|
||||||
switchable: true
|
|
||||||
// TODO: Custom date formatter
|
// TODO: Custom date formatter
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'line_items',
|
accessor: 'line_items',
|
||||||
title: t`Line Items`,
|
title: t`Line Items`,
|
||||||
sortable: true,
|
sortable: true
|
||||||
switchable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'total_price',
|
accessor: 'total_price',
|
||||||
title: t`Total Price`,
|
title: t`Total Price`,
|
||||||
sortable: true,
|
sortable: true
|
||||||
switchable: true
|
|
||||||
// TODO: Custom money formatter
|
// TODO: Custom money formatter
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'responsible',
|
accessor: 'responsible',
|
||||||
title: t`Responsible`,
|
title: t`Responsible`,
|
||||||
sortable: true,
|
sortable: true
|
||||||
switchable: true
|
|
||||||
// TODO: custom 'owner' formatter
|
// TODO: custom 'owner' formatter
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { ActionIcon, Stack, Text, Tooltip } from '@mantine/core';
|
import { Text } from '@mantine/core';
|
||||||
import { IconCirclePlus } from '@tabler/icons-react';
|
|
||||||
import { ReactNode, useCallback, useMemo } from 'react';
|
import { ReactNode, useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { supplierPartFields } from '../../../forms/CompanyForms';
|
import { supplierPartFields } from '../../../forms/CompanyForms';
|
||||||
@ -12,6 +11,7 @@ import {
|
|||||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||||
import { useUserState } from '../../../states/UserState';
|
import { useUserState } from '../../../states/UserState';
|
||||||
|
import { AddItemButton } from '../../buttons/AddItemButton';
|
||||||
import { Thumbnail } from '../../images/Thumbnail';
|
import { Thumbnail } from '../../images/Thumbnail';
|
||||||
import { TableColumn } from '../Column';
|
import { TableColumn } from '../Column';
|
||||||
import { InvenTreeTable } from '../InvenTreeTable';
|
import { InvenTreeTable } from '../InvenTreeTable';
|
||||||
@ -66,12 +66,11 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
|
|||||||
{
|
{
|
||||||
accessor: 'description',
|
accessor: 'description',
|
||||||
title: t`Description`,
|
title: t`Description`,
|
||||||
sortable: false,
|
sortable: false
|
||||||
switchable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'manufacturer',
|
accessor: 'manufacturer',
|
||||||
switchable: true,
|
|
||||||
sortable: true,
|
sortable: true,
|
||||||
title: t`Manufacturer`,
|
title: t`Manufacturer`,
|
||||||
render: (record: any) => {
|
render: (record: any) => {
|
||||||
@ -87,7 +86,7 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'MPN',
|
accessor: 'MPN',
|
||||||
switchable: true,
|
|
||||||
sortable: true,
|
sortable: true,
|
||||||
title: t`MPN`,
|
title: t`MPN`,
|
||||||
render: (record: any) => record?.manufacturer_part_detail?.MPN
|
render: (record: any) => record?.manufacturer_part_detail?.MPN
|
||||||
@ -95,20 +94,18 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
|
|||||||
{
|
{
|
||||||
accessor: 'in_stock',
|
accessor: 'in_stock',
|
||||||
title: t`In Stock`,
|
title: t`In Stock`,
|
||||||
sortable: true,
|
sortable: true
|
||||||
switchable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'packaging',
|
accessor: 'packaging',
|
||||||
title: t`Packaging`,
|
title: t`Packaging`,
|
||||||
sortable: true,
|
sortable: true
|
||||||
switchable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'pack_quantity',
|
accessor: 'pack_quantity',
|
||||||
title: t`Pack Quantity`,
|
title: t`Pack Quantity`,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
switchable: true,
|
|
||||||
render: (record: any) => {
|
render: (record: any) => {
|
||||||
let part = record?.part_detail ?? {};
|
let part = record?.part_detail ?? {};
|
||||||
|
|
||||||
@ -125,7 +122,7 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
|
|||||||
return (
|
return (
|
||||||
<TableHoverCard
|
<TableHoverCard
|
||||||
value={record.pack_quantity}
|
value={record.pack_quantity}
|
||||||
extra={extra.length > 0 && <Stack spacing="xs">{extra}</Stack>}
|
extra={extra}
|
||||||
title={t`Pack Quantity`}
|
title={t`Pack Quantity`}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -134,21 +131,20 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
|
|||||||
{
|
{
|
||||||
accessor: 'link',
|
accessor: 'link',
|
||||||
title: t`Link`,
|
title: t`Link`,
|
||||||
sortable: false,
|
sortable: false
|
||||||
switchable: true
|
|
||||||
// TODO: custom link renderer?
|
// TODO: custom link renderer?
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'note',
|
accessor: 'note',
|
||||||
title: t`Notes`,
|
title: t`Notes`,
|
||||||
sortable: false,
|
sortable: false
|
||||||
switchable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'available',
|
accessor: 'available',
|
||||||
title: t`Availability`,
|
title: t`Availability`,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
switchable: true,
|
|
||||||
render: (record: any) => {
|
render: (record: any) => {
|
||||||
let extra = [];
|
let extra = [];
|
||||||
|
|
||||||
@ -160,12 +156,7 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <TableHoverCard value={record.available} extra={extra} />;
|
||||||
<TableHoverCard
|
|
||||||
value={record.available}
|
|
||||||
extra={extra.length > 0 && <Stack spacing="xs">{extra}</Stack>}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@ -191,12 +182,7 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
|
|||||||
// TODO: Hide actions based on user permissions
|
// TODO: Hide actions based on user permissions
|
||||||
|
|
||||||
return [
|
return [
|
||||||
// TODO: Refactor this component out to something reusable
|
<AddItemButton tooltip={t`Add supplier part`} onClick={addSupplierPart} />
|
||||||
<Tooltip label={t`Add supplier part`}>
|
|
||||||
<ActionIcon radius="sm" onClick={addSupplierPart}>
|
|
||||||
<IconCirclePlus color="green" />
|
|
||||||
</ActionIcon>
|
|
||||||
</Tooltip>
|
|
||||||
];
|
];
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
|
@ -29,8 +29,7 @@ export function ReturnOrderTable({ params }: { params?: any }) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'description',
|
accessor: 'description',
|
||||||
title: t`Description`,
|
title: t`Description`
|
||||||
switchable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'customer__name',
|
accessor: 'customer__name',
|
||||||
@ -50,20 +49,19 @@ export function ReturnOrderTable({ params }: { params?: any }) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'customer_reference',
|
accessor: 'customer_reference',
|
||||||
title: t`Customer Reference`,
|
title: t`Customer Reference`
|
||||||
switchable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'project_code',
|
accessor: 'project_code',
|
||||||
title: t`Project Code`,
|
title: t`Project Code`
|
||||||
switchable: true
|
|
||||||
// TODO: Custom formatter
|
// TODO: Custom formatter
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'status',
|
accessor: 'status',
|
||||||
title: t`Status`,
|
title: t`Status`,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
switchable: true,
|
|
||||||
render: TableStatusRenderer(ModelType.returnorder)
|
render: TableStatusRenderer(ModelType.returnorder)
|
||||||
}
|
}
|
||||||
// TODO: Creation date
|
// TODO: Creation date
|
||||||
|
@ -30,8 +30,7 @@ export function SalesOrderTable({ params }: { params?: any }) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'description',
|
accessor: 'description',
|
||||||
title: t`Description`,
|
title: t`Description`
|
||||||
switchable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'customer__name',
|
accessor: 'customer__name',
|
||||||
@ -51,20 +50,19 @@ export function SalesOrderTable({ params }: { params?: any }) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'customer_reference',
|
accessor: 'customer_reference',
|
||||||
title: t`Customer Reference`,
|
title: t`Customer Reference`
|
||||||
switchable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'project_code',
|
accessor: 'project_code',
|
||||||
title: t`Project Code`,
|
title: t`Project Code`
|
||||||
switchable: true
|
|
||||||
// TODO: Custom formatter
|
// TODO: Custom formatter
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'status',
|
accessor: 'status',
|
||||||
title: t`Status`,
|
title: t`Status`,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
switchable: true,
|
|
||||||
render: TableStatusRenderer(ModelType.salesorder)
|
render: TableStatusRenderer(ModelType.salesorder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { ActionIcon, Text, Tooltip } from '@mantine/core';
|
import { Text } from '@mantine/core';
|
||||||
import { IconCirclePlus } from '@tabler/icons-react';
|
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -10,6 +9,7 @@ import {
|
|||||||
} from '../../../functions/forms';
|
} from '../../../functions/forms';
|
||||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||||
|
import { AddItemButton } from '../../buttons/AddItemButton';
|
||||||
import { TableColumn } from '../Column';
|
import { TableColumn } from '../Column';
|
||||||
import { InvenTreeTable } from '../InvenTreeTable';
|
import { InvenTreeTable } from '../InvenTreeTable';
|
||||||
import { RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
|
import { RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
|
||||||
@ -96,11 +96,8 @@ export function CustomUnitsTable() {
|
|||||||
let actions = [];
|
let actions = [];
|
||||||
|
|
||||||
actions.push(
|
actions.push(
|
||||||
<Tooltip label={t`Add custom unit`}>
|
// TODO: Adjust actions based on user permissions
|
||||||
<ActionIcon radius="sm" onClick={addCustomUnit}>
|
<AddItemButton tooltip={t`Add custom unit`} onClick={addCustomUnit} />
|
||||||
<IconCirclePlus color="green" />
|
|
||||||
</ActionIcon>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return actions;
|
return actions;
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { ActionIcon, Text, Tooltip } from '@mantine/core';
|
import { Text } from '@mantine/core';
|
||||||
import { IconCirclePlus } from '@tabler/icons-react';
|
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -10,6 +9,7 @@ import {
|
|||||||
} from '../../../functions/forms';
|
} from '../../../functions/forms';
|
||||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||||
|
import { AddItemButton } from '../../buttons/AddItemButton';
|
||||||
import { TableColumn } from '../Column';
|
import { TableColumn } from '../Column';
|
||||||
import { InvenTreeTable } from '../InvenTreeTable';
|
import { InvenTreeTable } from '../InvenTreeTable';
|
||||||
import { RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
|
import { RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
|
||||||
@ -86,11 +86,7 @@ export function ProjectCodeTable() {
|
|||||||
let actions = [];
|
let actions = [];
|
||||||
|
|
||||||
actions.push(
|
actions.push(
|
||||||
<Tooltip label={t`Add project code`}>
|
<AddItemButton onClick={addProjectCode} tooltip={t`Add project code`} />
|
||||||
<ActionIcon radius="sm" onClick={addProjectCode}>
|
|
||||||
<IconCirclePlus color="green" />
|
|
||||||
</ActionIcon>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return actions;
|
return actions;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Group, Stack, Text } from '@mantine/core';
|
import { Group, Text } from '@mantine/core';
|
||||||
import { ReactNode, useMemo } from 'react';
|
import { ReactNode, useMemo } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ function stockItemTableColumns(): TableColumn[] {
|
|||||||
{
|
{
|
||||||
accessor: 'part_detail.description',
|
accessor: 'part_detail.description',
|
||||||
sortable: false,
|
sortable: false,
|
||||||
switchable: true,
|
|
||||||
title: t`Description`
|
title: t`Description`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -145,7 +145,7 @@ function stockItemTableColumns(): TableColumn[] {
|
|||||||
</Group>
|
</Group>
|
||||||
}
|
}
|
||||||
title={t`Stock Information`}
|
title={t`Stock Information`}
|
||||||
extra={extra.length > 0 && <Stack spacing="xs">{extra}</Stack>}
|
extra={extra}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -153,7 +153,7 @@ function stockItemTableColumns(): TableColumn[] {
|
|||||||
{
|
{
|
||||||
accessor: 'status',
|
accessor: 'status',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
switchable: true,
|
|
||||||
filter: true,
|
filter: true,
|
||||||
title: t`Status`,
|
title: t`Status`,
|
||||||
render: TableStatusRenderer(ModelType.stockitem)
|
render: TableStatusRenderer(ModelType.stockitem)
|
||||||
@ -161,13 +161,13 @@ function stockItemTableColumns(): TableColumn[] {
|
|||||||
{
|
{
|
||||||
accessor: 'batch',
|
accessor: 'batch',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
switchable: true,
|
|
||||||
title: t`Batch`
|
title: t`Batch`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'location',
|
accessor: 'location',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
switchable: true,
|
|
||||||
title: t`Location`,
|
title: t`Location`,
|
||||||
render: function (record: any) {
|
render: function (record: any) {
|
||||||
// TODO: Custom renderer for location
|
// TODO: Custom renderer for location
|
||||||
|
@ -25,39 +25,37 @@ export function StockLocationTable({ params = {} }: { params?: any }) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'description',
|
accessor: 'description',
|
||||||
title: t`Description`,
|
title: t`Description`
|
||||||
switchable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'pathstring',
|
accessor: 'pathstring',
|
||||||
title: t`Path`,
|
title: t`Path`,
|
||||||
sortable: true,
|
sortable: true
|
||||||
switchable: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'items',
|
accessor: 'items',
|
||||||
title: t`Stock Items`,
|
title: t`Stock Items`,
|
||||||
switchable: true,
|
|
||||||
sortable: true
|
sortable: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'structural',
|
accessor: 'structural',
|
||||||
title: t`Structural`,
|
title: t`Structural`,
|
||||||
switchable: true,
|
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (record: any) => <YesNoButton value={record.structural} />
|
render: (record: any) => <YesNoButton value={record.structural} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'external',
|
accessor: 'external',
|
||||||
title: t`External`,
|
title: t`External`,
|
||||||
switchable: true,
|
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (record: any) => <YesNoButton value={record.external} />
|
render: (record: any) => <YesNoButton value={record.external} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'location_type',
|
accessor: 'location_type',
|
||||||
title: t`Location Type`,
|
title: t`Location Type`,
|
||||||
switchable: true,
|
|
||||||
sortable: false,
|
sortable: false,
|
||||||
render: (record: any) => record.location_type_detail?.name
|
render: (record: any) => record.location_type_detail?.name
|
||||||
}
|
}
|
||||||
|
65
src/frontend/src/forms/PurchaseOrderForms.tsx
Normal file
65
src/frontend/src/forms/PurchaseOrderForms.tsx
Normal file
@ -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: <IconCurrencyDollar />
|
||||||
|
},
|
||||||
|
purchase_price_currency: {
|
||||||
|
icon: <IconCoins />
|
||||||
|
},
|
||||||
|
target_date: {
|
||||||
|
icon: <IconCalendar />
|
||||||
|
},
|
||||||
|
destination: {
|
||||||
|
icon: <IconSitemap />
|
||||||
|
},
|
||||||
|
notes: {
|
||||||
|
icon: <IconNotes />
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
icon: <IconLink />
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
@ -8,7 +8,6 @@ import {
|
|||||||
IconEdit,
|
IconEdit,
|
||||||
IconFileTypePdf,
|
IconFileTypePdf,
|
||||||
IconInfoCircle,
|
IconInfoCircle,
|
||||||
IconLink,
|
|
||||||
IconList,
|
IconList,
|
||||||
IconListCheck,
|
IconListCheck,
|
||||||
IconNotes,
|
IconNotes,
|
||||||
@ -16,13 +15,20 @@ import {
|
|||||||
IconPrinter,
|
IconPrinter,
|
||||||
IconQrcode,
|
IconQrcode,
|
||||||
IconSitemap,
|
IconSitemap,
|
||||||
IconTrash,
|
IconTrash
|
||||||
IconUnlink
|
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
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 { PageDetail } from '../../components/nav/PageDetail';
|
||||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||||
import { ModelType } from '../../components/render/ModelType';
|
import { ModelType } from '../../components/render/ModelType';
|
||||||
@ -179,23 +185,13 @@ export default function BuildDetail() {
|
|||||||
tooltip={t`Barcode Actions`}
|
tooltip={t`Barcode Actions`}
|
||||||
icon={<IconQrcode />}
|
icon={<IconQrcode />}
|
||||||
actions={[
|
actions={[
|
||||||
{
|
ViewBarcodeAction({}),
|
||||||
icon: <IconQrcode />,
|
LinkBarcodeAction({
|
||||||
name: t`View`,
|
|
||||||
tooltip: t`View part barcode`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <IconLink />,
|
|
||||||
name: t`Link Barcode`,
|
|
||||||
tooltip: t`Link custom barcode to part`,
|
|
||||||
disabled: build?.barcode_hash
|
disabled: build?.barcode_hash
|
||||||
},
|
}),
|
||||||
{
|
UnlinkBarcodeAction({
|
||||||
icon: <IconUnlink />,
|
|
||||||
name: t`Unlink Barcode`,
|
|
||||||
tooltip: t`Unlink custom barcode from part`,
|
|
||||||
disabled: !build?.barcode_hash
|
disabled: !build?.barcode_hash
|
||||||
}
|
})
|
||||||
]}
|
]}
|
||||||
/>,
|
/>,
|
||||||
<ActionDropdown
|
<ActionDropdown
|
||||||
@ -215,21 +211,9 @@ export default function BuildDetail() {
|
|||||||
tooltip={t`Build Order Actions`}
|
tooltip={t`Build Order Actions`}
|
||||||
icon={<IconDots />}
|
icon={<IconDots />}
|
||||||
actions={[
|
actions={[
|
||||||
{
|
EditItemAction({}),
|
||||||
icon: <IconEdit color="blue" />,
|
DuplicateItemAction({}),
|
||||||
name: t`Edit`,
|
DeleteItemAction({})
|
||||||
tooltip: t`Edit build order`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <IconCopy color="green" />,
|
|
||||||
name: t`Duplicate`,
|
|
||||||
tooltip: t`Duplicate build order`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <IconTrash color="red" />,
|
|
||||||
name: t`Delete`,
|
|
||||||
tooltip: t`Delete build order`
|
|
||||||
}
|
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
];
|
];
|
||||||
|
@ -4,7 +4,6 @@ import {
|
|||||||
IconBuildingFactory2,
|
IconBuildingFactory2,
|
||||||
IconBuildingWarehouse,
|
IconBuildingWarehouse,
|
||||||
IconDots,
|
IconDots,
|
||||||
IconEdit,
|
|
||||||
IconInfoCircle,
|
IconInfoCircle,
|
||||||
IconMap2,
|
IconMap2,
|
||||||
IconNotes,
|
IconNotes,
|
||||||
@ -12,7 +11,6 @@ import {
|
|||||||
IconPackages,
|
IconPackages,
|
||||||
IconPaperclip,
|
IconPaperclip,
|
||||||
IconShoppingCart,
|
IconShoppingCart,
|
||||||
IconTrash,
|
|
||||||
IconTruckDelivery,
|
IconTruckDelivery,
|
||||||
IconTruckReturn,
|
IconTruckReturn,
|
||||||
IconUsersGroup
|
IconUsersGroup
|
||||||
@ -20,7 +18,11 @@ import {
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
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 { Breadcrumb } from '../../components/nav/BreadcrumbList';
|
||||||
import { PageDetail } from '../../components/nav/PageDetail';
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
import { PanelGroup } from '../../components/nav/PanelGroup';
|
import { PanelGroup } from '../../components/nav/PanelGroup';
|
||||||
@ -169,12 +171,9 @@ export default function CompanyDetail(props: CompanyDetailProps) {
|
|||||||
tooltip={t`Company Actions`}
|
tooltip={t`Company Actions`}
|
||||||
icon={<IconDots />}
|
icon={<IconDots />}
|
||||||
actions={[
|
actions={[
|
||||||
{
|
EditItemAction({
|
||||||
icon: <IconEdit color="blue" />,
|
|
||||||
name: t`Edit`,
|
|
||||||
tooltip: t`Edit company`,
|
|
||||||
disabled: !canEdit,
|
disabled: !canEdit,
|
||||||
onClick: () => {
|
callback: () => {
|
||||||
if (company?.pk) {
|
if (company?.pk) {
|
||||||
editCompany({
|
editCompany({
|
||||||
pk: company?.pk,
|
pk: company?.pk,
|
||||||
@ -182,13 +181,10 @@ export default function CompanyDetail(props: CompanyDetailProps) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}),
|
||||||
{
|
DeleteItemAction({
|
||||||
icon: <IconTrash color="red" />,
|
|
||||||
name: t`Delete`,
|
|
||||||
tooltip: t`Delete company`,
|
|
||||||
disabled: !canDelete
|
disabled: !canDelete
|
||||||
}
|
})
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
];
|
];
|
||||||
|
@ -6,27 +6,21 @@ import {
|
|||||||
IconBuildingFactory2,
|
IconBuildingFactory2,
|
||||||
IconCalendarStats,
|
IconCalendarStats,
|
||||||
IconClipboardList,
|
IconClipboardList,
|
||||||
IconCopy,
|
|
||||||
IconCurrencyDollar,
|
IconCurrencyDollar,
|
||||||
IconDots,
|
IconDots,
|
||||||
IconEdit,
|
|
||||||
IconInfoCircle,
|
IconInfoCircle,
|
||||||
IconLayersLinked,
|
IconLayersLinked,
|
||||||
IconLink,
|
|
||||||
IconList,
|
IconList,
|
||||||
IconListTree,
|
IconListTree,
|
||||||
IconNotes,
|
IconNotes,
|
||||||
IconPackages,
|
IconPackages,
|
||||||
IconPaperclip,
|
IconPaperclip,
|
||||||
IconQrcode,
|
|
||||||
IconShoppingCart,
|
IconShoppingCart,
|
||||||
IconStack2,
|
IconStack2,
|
||||||
IconTestPipe,
|
IconTestPipe,
|
||||||
IconTools,
|
IconTools,
|
||||||
IconTransfer,
|
IconTransfer,
|
||||||
IconTrash,
|
|
||||||
IconTruckDelivery,
|
IconTruckDelivery,
|
||||||
IconUnlink,
|
|
||||||
IconVersions
|
IconVersions
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
@ -34,7 +28,13 @@ import { useParams } from 'react-router-dom';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
ActionDropdown,
|
ActionDropdown,
|
||||||
BarcodeActionDropdown
|
BarcodeActionDropdown,
|
||||||
|
DeleteItemAction,
|
||||||
|
DuplicateItemAction,
|
||||||
|
EditItemAction,
|
||||||
|
LinkBarcodeAction,
|
||||||
|
UnlinkBarcodeAction,
|
||||||
|
ViewBarcodeAction
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
import { PageDetail } from '../../components/nav/PageDetail';
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||||
@ -263,23 +263,13 @@ export default function PartDetail() {
|
|||||||
return [
|
return [
|
||||||
<BarcodeActionDropdown
|
<BarcodeActionDropdown
|
||||||
actions={[
|
actions={[
|
||||||
{
|
ViewBarcodeAction({}),
|
||||||
icon: <IconQrcode />,
|
LinkBarcodeAction({
|
||||||
name: t`View`,
|
|
||||||
tooltip: t`View part barcode`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <IconLink />,
|
|
||||||
name: t`Link Barcode`,
|
|
||||||
tooltip: t`Link custom barcode to part`,
|
|
||||||
disabled: part?.barcode_hash
|
disabled: part?.barcode_hash
|
||||||
},
|
}),
|
||||||
{
|
UnlinkBarcodeAction({
|
||||||
icon: <IconUnlink />,
|
|
||||||
name: t`Unlink Barcode`,
|
|
||||||
tooltip: t`Unlink custom barcode from part`,
|
|
||||||
disabled: !part?.barcode_hash
|
disabled: !part?.barcode_hash
|
||||||
}
|
})
|
||||||
]}
|
]}
|
||||||
/>,
|
/>,
|
||||||
<ActionDropdown
|
<ActionDropdown
|
||||||
@ -304,28 +294,19 @@ export default function PartDetail() {
|
|||||||
tooltip={t`Part Actions`}
|
tooltip={t`Part Actions`}
|
||||||
icon={<IconDots />}
|
icon={<IconDots />}
|
||||||
actions={[
|
actions={[
|
||||||
{
|
DuplicateItemAction({}),
|
||||||
icon: <IconEdit color="blue" />,
|
EditItemAction({
|
||||||
name: t`Edit`,
|
callback: () => {
|
||||||
tooltip: t`Edit part`,
|
|
||||||
onClick: () => {
|
|
||||||
part.pk &&
|
part.pk &&
|
||||||
editPart({
|
editPart({
|
||||||
part_id: part.pk,
|
part_id: part.pk,
|
||||||
callback: refreshInstance
|
callback: refreshInstance
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
}),
|
||||||
{
|
DeleteItemAction({
|
||||||
icon: <IconCopy color="green" />,
|
disabled: part?.active
|
||||||
name: t`Duplicate`,
|
})
|
||||||
tooltip: t`Duplicate part`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <IconTrash color="red" />,
|
|
||||||
name: t`Delete`,
|
|
||||||
tooltip: t`Delete part`
|
|
||||||
}
|
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
];
|
];
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { LoadingOverlay, Stack } from '@mantine/core';
|
import { LoadingOverlay, Stack } from '@mantine/core';
|
||||||
import {
|
import {
|
||||||
|
IconDots,
|
||||||
IconInfoCircle,
|
IconInfoCircle,
|
||||||
IconList,
|
IconList,
|
||||||
IconNotes,
|
IconNotes,
|
||||||
@ -10,13 +11,24 @@ import {
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
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 { PageDetail } from '../../components/nav/PageDetail';
|
||||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||||
import { AttachmentTable } from '../../components/tables/general/AttachmentTable';
|
import { AttachmentTable } from '../../components/tables/general/AttachmentTable';
|
||||||
|
import { PurchaseOrderLineItemTable } from '../../components/tables/purchasing/PurchaseOrderLineItemTable';
|
||||||
import { StockItemTable } from '../../components/tables/stock/StockItemTable';
|
import { StockItemTable } from '../../components/tables/stock/StockItemTable';
|
||||||
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
|
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
|
||||||
import { useInstance } from '../../hooks/UseInstance';
|
import { useInstance } from '../../hooks/UseInstance';
|
||||||
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
||||||
|
import { useUserState } from '../../states/UserState';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detail page for a single PurchaseOrder
|
* Detail page for a single PurchaseOrder
|
||||||
@ -24,6 +36,8 @@ import { ApiPaths, apiUrl } from '../../states/ApiState';
|
|||||||
export default function PurchaseOrderDetail() {
|
export default function PurchaseOrderDetail() {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
|
||||||
|
const user = useUserState();
|
||||||
|
|
||||||
const { instance: order, instanceQuery } = useInstance({
|
const { instance: order, instanceQuery } = useInstance({
|
||||||
endpoint: ApiPaths.purchase_order_list,
|
endpoint: ApiPaths.purchase_order_list,
|
||||||
pk: id,
|
pk: id,
|
||||||
@ -43,7 +57,8 @@ export default function PurchaseOrderDetail() {
|
|||||||
{
|
{
|
||||||
name: 'line-items',
|
name: 'line-items',
|
||||||
label: t`Line Items`,
|
label: t`Line Items`,
|
||||||
icon: <IconList />
|
icon: <IconList />,
|
||||||
|
content: order?.pk && <PurchaseOrderLineItemTable orderId={order.pk} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'received-stock',
|
name: 'received-stock',
|
||||||
@ -84,6 +99,29 @@ export default function PurchaseOrderDetail() {
|
|||||||
];
|
];
|
||||||
}, [order, id]);
|
}, [order, id]);
|
||||||
|
|
||||||
|
const poActions = useMemo(() => {
|
||||||
|
// TODO: Disable certain actions based on user permissions
|
||||||
|
return [
|
||||||
|
<BarcodeActionDropdown
|
||||||
|
actions={[
|
||||||
|
ViewBarcodeAction({}),
|
||||||
|
LinkBarcodeAction({
|
||||||
|
disabled: order?.barcode_hash
|
||||||
|
}),
|
||||||
|
UnlinkBarcodeAction({
|
||||||
|
disabled: !order?.barcode_hash
|
||||||
|
})
|
||||||
|
]}
|
||||||
|
/>,
|
||||||
|
<ActionDropdown
|
||||||
|
key="order"
|
||||||
|
tooltip={t`Order Actions`}
|
||||||
|
icon={<IconDots />}
|
||||||
|
actions={[EditItemAction({}), DeleteItemAction({})]}
|
||||||
|
/>
|
||||||
|
];
|
||||||
|
}, [id, order, user]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Stack spacing="xs">
|
<Stack spacing="xs">
|
||||||
@ -93,6 +131,7 @@ export default function PurchaseOrderDetail() {
|
|||||||
subtitle={order.description}
|
subtitle={order.description}
|
||||||
imageUrl={order.supplier_detail?.image}
|
imageUrl={order.supplier_detail?.image}
|
||||||
breadcrumbs={[{ name: t`Purchasing`, url: '/purchasing/' }]}
|
breadcrumbs={[{ name: t`Purchasing`, url: '/purchasing/' }]}
|
||||||
|
actions={poActions}
|
||||||
/>
|
/>
|
||||||
<PanelGroup pageKey="purchaseorder" panels={orderPanels} />
|
<PanelGroup pageKey="purchaseorder" panels={orderPanels} />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -9,25 +9,25 @@ import {
|
|||||||
IconCirclePlus,
|
IconCirclePlus,
|
||||||
IconCopy,
|
IconCopy,
|
||||||
IconDots,
|
IconDots,
|
||||||
IconEdit,
|
|
||||||
IconHistory,
|
IconHistory,
|
||||||
IconInfoCircle,
|
IconInfoCircle,
|
||||||
IconLink,
|
|
||||||
IconNotes,
|
IconNotes,
|
||||||
IconPackages,
|
IconPackages,
|
||||||
IconPaperclip,
|
IconPaperclip,
|
||||||
IconQrcode,
|
|
||||||
IconSitemap,
|
IconSitemap,
|
||||||
IconTransfer,
|
IconTransfer
|
||||||
IconTrash,
|
|
||||||
IconUnlink
|
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ActionDropdown,
|
ActionDropdown,
|
||||||
BarcodeActionDropdown
|
BarcodeActionDropdown,
|
||||||
|
DeleteItemAction,
|
||||||
|
EditItemAction,
|
||||||
|
LinkBarcodeAction,
|
||||||
|
UnlinkBarcodeAction,
|
||||||
|
ViewBarcodeAction
|
||||||
} from '../../components/items/ActionDropdown';
|
} from '../../components/items/ActionDropdown';
|
||||||
import { PlaceholderPanel } from '../../components/items/Placeholder';
|
import { PlaceholderPanel } from '../../components/items/Placeholder';
|
||||||
import { PageDetail } from '../../components/nav/PageDetail';
|
import { PageDetail } from '../../components/nav/PageDetail';
|
||||||
@ -144,23 +144,13 @@ export default function StockDetail() {
|
|||||||
() => /* TODO: Disable actions based on user permissions*/ [
|
() => /* TODO: Disable actions based on user permissions*/ [
|
||||||
<BarcodeActionDropdown
|
<BarcodeActionDropdown
|
||||||
actions={[
|
actions={[
|
||||||
{
|
ViewBarcodeAction({}),
|
||||||
icon: <IconQrcode />,
|
LinkBarcodeAction({
|
||||||
name: t`View`,
|
|
||||||
tooltip: t`View part barcode`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <IconLink />,
|
|
||||||
name: t`Link Barcode`,
|
|
||||||
tooltip: t`Link custom barcode to stock item`,
|
|
||||||
disabled: stockitem?.barcode_hash
|
disabled: stockitem?.barcode_hash
|
||||||
},
|
}),
|
||||||
{
|
UnlinkBarcodeAction({
|
||||||
icon: <IconUnlink />,
|
|
||||||
name: t`Unlink Barcode`,
|
|
||||||
tooltip: t`Unlink custom barcode from stock item`,
|
|
||||||
disabled: !stockitem?.barcode_hash
|
disabled: !stockitem?.barcode_hash
|
||||||
}
|
})
|
||||||
]}
|
]}
|
||||||
/>,
|
/>,
|
||||||
<ActionDropdown
|
<ActionDropdown
|
||||||
@ -200,23 +190,16 @@ export default function StockDetail() {
|
|||||||
tooltip: t`Duplicate stock item`,
|
tooltip: t`Duplicate stock item`,
|
||||||
icon: <IconCopy />
|
icon: <IconCopy />
|
||||||
},
|
},
|
||||||
{
|
EditItemAction({
|
||||||
name: t`Edit`,
|
callback: () => {
|
||||||
tooltip: t`Edit stock item`,
|
|
||||||
icon: <IconEdit color="blue" />,
|
|
||||||
onClick: () => {
|
|
||||||
stockitem.pk &&
|
stockitem.pk &&
|
||||||
editStockItem({
|
editStockItem({
|
||||||
item_id: stockitem.pk,
|
item_id: stockitem.pk,
|
||||||
callback: () => refreshInstance
|
callback: () => refreshInstance
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
}),
|
||||||
{
|
DeleteItemAction({})
|
||||||
name: t`Delete`,
|
|
||||||
tooltip: t`Delete stock item`,
|
|
||||||
icon: <IconTrash color="red" />
|
|
||||||
}
|
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
],
|
],
|
||||||
|
@ -106,6 +106,7 @@ export enum ApiPaths {
|
|||||||
|
|
||||||
// Purchase Order URLs
|
// Purchase Order URLs
|
||||||
purchase_order_list = 'api-purchase-order-list',
|
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',
|
purchase_order_attachment_list = 'api-purchase-order-attachment-list',
|
||||||
|
|
||||||
// Sales Order URLs
|
// Sales Order URLs
|
||||||
@ -218,6 +219,8 @@ export function apiEndpoint(path: ApiPaths): string {
|
|||||||
return 'stock/attachment/';
|
return 'stock/attachment/';
|
||||||
case ApiPaths.purchase_order_list:
|
case ApiPaths.purchase_order_list:
|
||||||
return 'order/po/';
|
return 'order/po/';
|
||||||
|
case ApiPaths.purchase_order_line_list:
|
||||||
|
return 'order/po-line/';
|
||||||
case ApiPaths.purchase_order_attachment_list:
|
case ApiPaths.purchase_order_attachment_list:
|
||||||
return 'order/po/attachment/';
|
return 'order/po/attachment/';
|
||||||
case ApiPaths.sales_order_list:
|
case ApiPaths.sales_order_list:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user