mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +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 { 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: <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
|
||||
*/
|
||||
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 <RenderInlineModel primary={text} secondary={part.full_name} />;
|
||||
return (
|
||||
<RenderInlineModel
|
||||
primary={supplier?.name}
|
||||
secondary={instance.SKU}
|
||||
image={part?.thumbnail ?? part?.image}
|
||||
suffix={part.full_name}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,14 +5,16 @@ import { RenderInlineModel } from './Instance';
|
||||
export function RenderOwner({ instance }: { instance: any }): ReactNode {
|
||||
// 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 {
|
||||
return (
|
||||
<RenderInlineModel
|
||||
primary={instance.username}
|
||||
secondary={`${instance.first_name} ${instance.last_name}`}
|
||||
/>
|
||||
instance && (
|
||||
<RenderInlineModel
|
||||
primary={instance.username}
|
||||
secondary={`${instance.first_name} ${instance.last_name}`}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ export const PurchaseOrderRenderer = ({ pk }: { pk: string }) => {
|
||||
return (
|
||||
<GeneralRenderer
|
||||
api_key={ApiPaths.purchase_order_list}
|
||||
api_ref="pruchaseorder"
|
||||
api_ref="purchaseorder"
|
||||
link={`/order/purchase-order/${pk}`}
|
||||
pk={pk}
|
||||
renderer={DetailRenderer}
|
||||
|
@ -15,4 +15,5 @@ export type TableColumn = {
|
||||
noWrap?: boolean; // Whether the column should wrap
|
||||
ellipsis?: boolean; // Whether the column should be ellipsized
|
||||
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.Label>{t`Select Columns`}</Menu.Label>
|
||||
{columns
|
||||
.filter((col) => col.switchable)
|
||||
.filter((col) => col.switchable ?? true)
|
||||
.map((col) => (
|
||||
<Menu.Item key={col.accessor}>
|
||||
<Checkbox
|
||||
|
@ -9,7 +9,7 @@ import { DataTable, DataTableSortStatus } from 'mantine-datatable';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { api } from '../../App';
|
||||
import { ButtonMenu } from '../items/ButtonMenu';
|
||||
import { ButtonMenu } from '../buttons/ButtonMenu';
|
||||
import { TableColumn } from './Column';
|
||||
import { TableColumnSelect } from './ColumnSelect';
|
||||
import { DownloadAction } from './DownloadAction';
|
||||
@ -82,7 +82,6 @@ const defaultInvenTreeTableProps: InvenTreeTableProps = {
|
||||
customFilters: [],
|
||||
customActionGroups: [],
|
||||
idAccessor: 'pk',
|
||||
rowActions: (record: any) => [],
|
||||
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 (
|
||||
<RowActions
|
||||
@ -445,12 +453,6 @@ export function InvenTreeTable({
|
||||
actions={tableProps.printingActions ?? []}
|
||||
/>
|
||||
)}
|
||||
{tableProps.enableDownload && (
|
||||
<DownloadAction
|
||||
key="download-action"
|
||||
downloadCallback={downloadData}
|
||||
/>
|
||||
)}
|
||||
</Group>
|
||||
<Space />
|
||||
<Group position="right" spacing={5}>
|
||||
@ -488,6 +490,12 @@ export function InvenTreeTable({
|
||||
</ActionIcon>
|
||||
</Indicator>
|
||||
)}
|
||||
{tableProps.enableDownload && (
|
||||
<DownloadAction
|
||||
key="download-action"
|
||||
downloadCallback={downloadData}
|
||||
/>
|
||||
)}
|
||||
</Group>
|
||||
</Group>
|
||||
{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}
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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({
|
||||
<Stack spacing="xs">
|
||||
<Group spacing="xs" position="left">
|
||||
<IconInfoCircle size="16" color="blue" />
|
||||
{title}
|
||||
<Text weight="bold">{title}</Text>
|
||||
</Group>
|
||||
<Divider />
|
||||
{extra}
|
||||
|
@ -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 <YesNoButton value={row.optional} />;
|
||||
@ -89,7 +88,7 @@ export function BomTable({
|
||||
{
|
||||
accessor: 'consumable',
|
||||
title: t`Consumable`,
|
||||
switchable: true,
|
||||
|
||||
sortable: true,
|
||||
render: (row) => {
|
||||
return <YesNoButton value={row.consumable} />;
|
||||
@ -98,7 +97,7 @@ export function BomTable({
|
||||
{
|
||||
accessor: 'allow_variants',
|
||||
title: t`Allow Variants`,
|
||||
switchable: true,
|
||||
|
||||
sortable: true,
|
||||
render: (row) => {
|
||||
return <YesNoButton value={row.allow_variants} />;
|
||||
@ -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 (
|
||||
<TableHoverCard
|
||||
value={available_stock}
|
||||
extra={
|
||||
extra.length > 0 ? <Stack spacing="xs">{extra}</Stack> : 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
|
||||
},
|
||||
|
@ -73,8 +73,7 @@ export function UsedInTable({
|
||||
{
|
||||
accessor: 'reference',
|
||||
title: t`Reference`,
|
||||
sortable: true,
|
||||
switchable: true
|
||||
sortable: true
|
||||
}
|
||||
];
|
||||
}, [partId]);
|
||||
|
@ -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 (
|
||||
<Progress
|
||||
value={progress}
|
||||
label={record.completed}
|
||||
color={progress < 100 ? 'blue' : 'green'}
|
||||
size="xl"
|
||||
radius="xl"
|
||||
/>
|
||||
);
|
||||
}
|
||||
render: (record: any) => (
|
||||
<ProgressBar
|
||||
progressLabel={true}
|
||||
value={record.completed}
|
||||
maximum={record.quantity}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
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 ? (
|
||||
<TableHoverCard
|
||||
value={project.code}
|
||||
title={t`Project Code`}
|
||||
extra={<Text>{project.description}</Text>}
|
||||
/>
|
||||
) : (
|
||||
'-'
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
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) => (
|
||||
<RenderUser instance={record?.issued_by_detail} />
|
||||
)
|
||||
},
|
||||
{
|
||||
accessor: 'responsible',
|
||||
sortable: true,
|
||||
title: t`Responsible`,
|
||||
switchable: true
|
||||
// TODO: custom render function
|
||||
render: (record: any) => (
|
||||
<RenderOwner instance={record?.responsible_detail} />
|
||||
)
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -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 (
|
||||
<Group position="apart">
|
||||
|
@ -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
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
|
@ -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
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
|
@ -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(
|
||||
<Tooltip label={t`Add parameter`}>
|
||||
<ActionIcon radius="sm" onClick={addParameter}>
|
||||
<IconTextPlus color="green" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<AddItemButton tooltip="Add parameter" onClick={addParameter} />
|
||||
);
|
||||
|
||||
return actions;
|
||||
|
@ -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[] {
|
||||
</Group>
|
||||
}
|
||||
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',
|
||||
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`
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
],
|
||||
[]
|
||||
|
@ -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',
|
||||
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
|
||||
}
|
||||
];
|
||||
|
@ -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 (
|
||||
<TableHoverCard
|
||||
value={record.pack_quantity}
|
||||
extra={extra.length > 0 && <Stack spacing="xs">{extra}</Stack>}
|
||||
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 (
|
||||
<TableHoverCard
|
||||
value={record.available}
|
||||
extra={extra.length > 0 && <Stack spacing="xs">{extra}</Stack>}
|
||||
/>
|
||||
);
|
||||
return <TableHoverCard value={record.available} extra={extra} />;
|
||||
}
|
||||
}
|
||||
];
|
||||
@ -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
|
||||
<Tooltip label={t`Add supplier part`}>
|
||||
<ActionIcon radius="sm" onClick={addSupplierPart}>
|
||||
<IconCirclePlus color="green" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<AddItemButton tooltip={t`Add supplier part`} onClick={addSupplierPart} />
|
||||
];
|
||||
}, [user]);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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(
|
||||
<Tooltip label={t`Add custom unit`}>
|
||||
<ActionIcon radius="sm" onClick={addCustomUnit}>
|
||||
<IconCirclePlus color="green" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
// TODO: Adjust actions based on user permissions
|
||||
<AddItemButton tooltip={t`Add custom unit`} onClick={addCustomUnit} />
|
||||
);
|
||||
|
||||
return actions;
|
||||
|
@ -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(
|
||||
<Tooltip label={t`Add project code`}>
|
||||
<ActionIcon radius="sm" onClick={addProjectCode}>
|
||||
<IconCirclePlus color="green" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<AddItemButton onClick={addProjectCode} tooltip={t`Add project code`} />
|
||||
);
|
||||
|
||||
return actions;
|
||||
|
@ -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[] {
|
||||
</Group>
|
||||
}
|
||||
title={t`Stock Information`}
|
||||
extra={extra.length > 0 && <Stack spacing="xs">{extra}</Stack>}
|
||||
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
|
||||
|
@ -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) => <YesNoButton value={record.structural} />
|
||||
},
|
||||
{
|
||||
accessor: 'external',
|
||||
title: t`External`,
|
||||
switchable: true,
|
||||
|
||||
sortable: true,
|
||||
render: (record: any) => <YesNoButton value={record.external} />
|
||||
},
|
||||
{
|
||||
accessor: 'location_type',
|
||||
title: t`Location Type`,
|
||||
switchable: true,
|
||||
|
||||
sortable: false,
|
||||
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,
|
||||
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={<IconQrcode />}
|
||||
actions={[
|
||||
{
|
||||
icon: <IconQrcode />,
|
||||
name: t`View`,
|
||||
tooltip: t`View part barcode`
|
||||
},
|
||||
{
|
||||
icon: <IconLink />,
|
||||
name: t`Link Barcode`,
|
||||
tooltip: t`Link custom barcode to part`,
|
||||
ViewBarcodeAction({}),
|
||||
LinkBarcodeAction({
|
||||
disabled: build?.barcode_hash
|
||||
},
|
||||
{
|
||||
icon: <IconUnlink />,
|
||||
name: t`Unlink Barcode`,
|
||||
tooltip: t`Unlink custom barcode from part`,
|
||||
}),
|
||||
UnlinkBarcodeAction({
|
||||
disabled: !build?.barcode_hash
|
||||
}
|
||||
})
|
||||
]}
|
||||
/>,
|
||||
<ActionDropdown
|
||||
@ -215,21 +211,9 @@ export default function BuildDetail() {
|
||||
tooltip={t`Build Order Actions`}
|
||||
icon={<IconDots />}
|
||||
actions={[
|
||||
{
|
||||
icon: <IconEdit color="blue" />,
|
||||
name: t`Edit`,
|
||||
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`
|
||||
}
|
||||
EditItemAction({}),
|
||||
DuplicateItemAction({}),
|
||||
DeleteItemAction({})
|
||||
]}
|
||||
/>
|
||||
];
|
||||
|
@ -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={<IconDots />}
|
||||
actions={[
|
||||
{
|
||||
icon: <IconEdit color="blue" />,
|
||||
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: <IconTrash color="red" />,
|
||||
name: t`Delete`,
|
||||
tooltip: t`Delete company`,
|
||||
}),
|
||||
DeleteItemAction({
|
||||
disabled: !canDelete
|
||||
}
|
||||
})
|
||||
]}
|
||||
/>
|
||||
];
|
||||
|
@ -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 [
|
||||
<BarcodeActionDropdown
|
||||
actions={[
|
||||
{
|
||||
icon: <IconQrcode />,
|
||||
name: t`View`,
|
||||
tooltip: t`View part barcode`
|
||||
},
|
||||
{
|
||||
icon: <IconLink />,
|
||||
name: t`Link Barcode`,
|
||||
tooltip: t`Link custom barcode to part`,
|
||||
ViewBarcodeAction({}),
|
||||
LinkBarcodeAction({
|
||||
disabled: part?.barcode_hash
|
||||
},
|
||||
{
|
||||
icon: <IconUnlink />,
|
||||
name: t`Unlink Barcode`,
|
||||
tooltip: t`Unlink custom barcode from part`,
|
||||
}),
|
||||
UnlinkBarcodeAction({
|
||||
disabled: !part?.barcode_hash
|
||||
}
|
||||
})
|
||||
]}
|
||||
/>,
|
||||
<ActionDropdown
|
||||
@ -304,28 +294,19 @@ export default function PartDetail() {
|
||||
tooltip={t`Part Actions`}
|
||||
icon={<IconDots />}
|
||||
actions={[
|
||||
{
|
||||
icon: <IconEdit color="blue" />,
|
||||
name: t`Edit`,
|
||||
tooltip: t`Edit part`,
|
||||
onClick: () => {
|
||||
DuplicateItemAction({}),
|
||||
EditItemAction({
|
||||
callback: () => {
|
||||
part.pk &&
|
||||
editPart({
|
||||
part_id: part.pk,
|
||||
callback: refreshInstance
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: <IconCopy color="green" />,
|
||||
name: t`Duplicate`,
|
||||
tooltip: t`Duplicate part`
|
||||
},
|
||||
{
|
||||
icon: <IconTrash color="red" />,
|
||||
name: t`Delete`,
|
||||
tooltip: t`Delete part`
|
||||
}
|
||||
}),
|
||||
DeleteItemAction({
|
||||
disabled: part?.active
|
||||
})
|
||||
]}
|
||||
/>
|
||||
];
|
||||
|
@ -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: <IconList />
|
||||
icon: <IconList />,
|
||||
content: order?.pk && <PurchaseOrderLineItemTable orderId={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 [
|
||||
<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 (
|
||||
<>
|
||||
<Stack spacing="xs">
|
||||
@ -93,6 +131,7 @@ export default function PurchaseOrderDetail() {
|
||||
subtitle={order.description}
|
||||
imageUrl={order.supplier_detail?.image}
|
||||
breadcrumbs={[{ name: t`Purchasing`, url: '/purchasing/' }]}
|
||||
actions={poActions}
|
||||
/>
|
||||
<PanelGroup pageKey="purchaseorder" panels={orderPanels} />
|
||||
</Stack>
|
||||
|
@ -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*/ [
|
||||
<BarcodeActionDropdown
|
||||
actions={[
|
||||
{
|
||||
icon: <IconQrcode />,
|
||||
name: t`View`,
|
||||
tooltip: t`View part barcode`
|
||||
},
|
||||
{
|
||||
icon: <IconLink />,
|
||||
name: t`Link Barcode`,
|
||||
tooltip: t`Link custom barcode to stock item`,
|
||||
ViewBarcodeAction({}),
|
||||
LinkBarcodeAction({
|
||||
disabled: stockitem?.barcode_hash
|
||||
},
|
||||
{
|
||||
icon: <IconUnlink />,
|
||||
name: t`Unlink Barcode`,
|
||||
tooltip: t`Unlink custom barcode from stock item`,
|
||||
}),
|
||||
UnlinkBarcodeAction({
|
||||
disabled: !stockitem?.barcode_hash
|
||||
}
|
||||
})
|
||||
]}
|
||||
/>,
|
||||
<ActionDropdown
|
||||
@ -200,23 +190,16 @@ export default function StockDetail() {
|
||||
tooltip: t`Duplicate stock item`,
|
||||
icon: <IconCopy />
|
||||
},
|
||||
{
|
||||
name: t`Edit`,
|
||||
tooltip: t`Edit stock item`,
|
||||
icon: <IconEdit color="blue" />,
|
||||
onClick: () => {
|
||||
EditItemAction({
|
||||
callback: () => {
|
||||
stockitem.pk &&
|
||||
editStockItem({
|
||||
item_id: stockitem.pk,
|
||||
callback: () => refreshInstance
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: t`Delete`,
|
||||
tooltip: t`Delete stock item`,
|
||||
icon: <IconTrash color="red" />
|
||||
}
|
||||
}),
|
||||
DeleteItemAction({})
|
||||
]}
|
||||
/>
|
||||
],
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user