mirror of
https://github.com/inventree/InvenTree.git
synced 2025-05-15 03:23:07 +00:00
[PUI] StockTrackingTable (#7273)
* Bare bones <StockTrackingTable /> component * Implement details panel for StockTrackingTable * Remove unused userState hook * Expand RenderInstance to include link * Allow inline renderers to display links
This commit is contained in:
parent
c1def12203
commit
76b298c43e
@ -1,17 +1,21 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
|
import { getDetailUrl } from '../../functions/urls';
|
||||||
import { InstanceRenderInterface, RenderInlineModel } from './Instance';
|
import { InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||||
import { StatusRenderer } from './StatusRenderer';
|
import { StatusRenderer } from './StatusRenderer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inline rendering of a single BuildOrder instance
|
* Inline rendering of a single BuildOrder instance
|
||||||
*/
|
*/
|
||||||
export function RenderBuildOrder({
|
export function RenderBuildOrder(
|
||||||
instance
|
props: Readonly<InstanceRenderInterface>
|
||||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
): ReactNode {
|
||||||
|
const { instance } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RenderInlineModel
|
<RenderInlineModel
|
||||||
|
{...props}
|
||||||
primary={instance.reference}
|
primary={instance.reference}
|
||||||
secondary={instance.title}
|
secondary={instance.title}
|
||||||
suffix={StatusRenderer({
|
suffix={StatusRenderer({
|
||||||
@ -19,6 +23,7 @@ export function RenderBuildOrder({
|
|||||||
type: ModelType.build
|
type: ModelType.build
|
||||||
})}
|
})}
|
||||||
image={instance.part_detail?.thumbnail || instance.part_detail?.image}
|
image={instance.part_detail?.thumbnail || instance.part_detail?.image}
|
||||||
|
url={props.link ? getDetailUrl(ModelType.build, instance.pk) : undefined}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
import { ModelType } from '../../enums/ModelType';
|
||||||
|
import { getDetailUrl } from '../../functions/urls';
|
||||||
import { InstanceRenderInterface, RenderInlineModel } from './Instance';
|
import { InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,16 +27,20 @@ export function RenderAddress({
|
|||||||
/**
|
/**
|
||||||
* Inline rendering of a single Company instance
|
* Inline rendering of a single Company instance
|
||||||
*/
|
*/
|
||||||
export function RenderCompany({
|
export function RenderCompany(
|
||||||
instance
|
props: Readonly<InstanceRenderInterface>
|
||||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
): ReactNode {
|
||||||
// TODO: Handle URL
|
const { instance } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RenderInlineModel
|
<RenderInlineModel
|
||||||
|
{...props}
|
||||||
image={instance.thumnbnail || instance.image}
|
image={instance.thumnbnail || instance.image}
|
||||||
primary={instance.name}
|
primary={instance.name}
|
||||||
secondary={instance.description}
|
secondary={instance.description}
|
||||||
|
url={
|
||||||
|
props.link ? getDetailUrl(ModelType.company, instance.pk) : undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -51,20 +57,25 @@ export function RenderContact({
|
|||||||
/**
|
/**
|
||||||
* Inline rendering of a single SupplierPart instance
|
* Inline rendering of a single SupplierPart instance
|
||||||
*/
|
*/
|
||||||
export function RenderSupplierPart({
|
export function RenderSupplierPart(
|
||||||
instance
|
props: Readonly<InstanceRenderInterface>
|
||||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
): ReactNode {
|
||||||
// TODO: handle URL
|
const { instance } = props;
|
||||||
|
const supplier = instance.supplier_detail ?? {};
|
||||||
let supplier = instance.supplier_detail ?? {};
|
const part = instance.part_detail ?? {};
|
||||||
let part = instance.part_detail ?? {};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RenderInlineModel
|
<RenderInlineModel
|
||||||
|
{...props}
|
||||||
primary={supplier?.name}
|
primary={supplier?.name}
|
||||||
secondary={instance.SKU}
|
secondary={instance.SKU}
|
||||||
image={part?.thumbnail ?? part?.image}
|
image={part?.thumbnail ?? part?.image}
|
||||||
suffix={part.full_name}
|
suffix={part.full_name}
|
||||||
|
url={
|
||||||
|
props.link
|
||||||
|
? getDetailUrl(ModelType.supplierpart, instance.pk)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -72,18 +83,25 @@ export function RenderSupplierPart({
|
|||||||
/**
|
/**
|
||||||
* Inline rendering of a single ManufacturerPart instance
|
* Inline rendering of a single ManufacturerPart instance
|
||||||
*/
|
*/
|
||||||
export function RenderManufacturerPart({
|
export function RenderManufacturerPart(
|
||||||
instance
|
props: Readonly<InstanceRenderInterface>
|
||||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
): ReactNode {
|
||||||
let part = instance.part_detail ?? {};
|
const { instance } = props;
|
||||||
let manufacturer = instance.manufacturer_detail ?? {};
|
const part = instance.part_detail ?? {};
|
||||||
|
const manufacturer = instance.manufacturer_detail ?? {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RenderInlineModel
|
<RenderInlineModel
|
||||||
|
{...props}
|
||||||
primary={manufacturer.name}
|
primary={manufacturer.name}
|
||||||
secondary={instance.MPN}
|
secondary={instance.MPN}
|
||||||
suffix={part.full_name}
|
suffix={part.full_name}
|
||||||
image={manufacturer?.thumnbnail ?? manufacturer.image}
|
image={manufacturer?.thumnbnail ?? manufacturer.image}
|
||||||
|
url={
|
||||||
|
props.link
|
||||||
|
? getDetailUrl(ModelType.manufacturerpart, instance.pk)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Alert, Group, Space, Text } from '@mantine/core';
|
import { Alert, Anchor, Group, Space, Text } from '@mantine/core';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode, useCallback } from 'react';
|
||||||
|
|
||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
|
import { navigateToLink } from '../../functions/navigation';
|
||||||
import { Thumbnail } from '../images/Thumbnail';
|
import { Thumbnail } from '../images/Thumbnail';
|
||||||
import { RenderBuildLine, RenderBuildOrder } from './Build';
|
import { RenderBuildLine, RenderBuildOrder } from './Build';
|
||||||
import {
|
import {
|
||||||
@ -36,6 +37,12 @@ type EnumDictionary<T extends string | symbol | number, U> = {
|
|||||||
[K in T]: U;
|
[K in T]: U;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface InstanceRenderInterface {
|
||||||
|
instance: any;
|
||||||
|
link?: boolean;
|
||||||
|
navigate?: any;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lookup table for rendering a model instance
|
* Lookup table for rendering a model instance
|
||||||
*/
|
*/
|
||||||
@ -68,31 +75,27 @@ const RendererLookup: EnumDictionary<
|
|||||||
[ModelType.user]: RenderUser
|
[ModelType.user]: RenderUser
|
||||||
};
|
};
|
||||||
|
|
||||||
// import { ApiFormFieldType } from "../forms/fields/ApiFormField";
|
export type RenderInstanceProps = {
|
||||||
|
model: ModelType | undefined;
|
||||||
|
} & InstanceRenderInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render an instance of a database model, depending on the provided data
|
* Render an instance of a database model, depending on the provided data
|
||||||
*/
|
*/
|
||||||
export function RenderInstance({
|
export function RenderInstance(props: RenderInstanceProps): ReactNode {
|
||||||
model,
|
if (props.model === undefined) {
|
||||||
instance
|
|
||||||
}: {
|
|
||||||
model: ModelType | undefined;
|
|
||||||
instance: any;
|
|
||||||
}): ReactNode {
|
|
||||||
if (model === undefined) {
|
|
||||||
console.error('RenderInstance: No model provided');
|
console.error('RenderInstance: No model provided');
|
||||||
return <UnknownRenderer model={model} />;
|
return <UnknownRenderer model={props.model} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RenderComponent = RendererLookup[model];
|
const RenderComponent = RendererLookup[props.model];
|
||||||
|
|
||||||
if (!RenderComponent) {
|
if (!RenderComponent) {
|
||||||
console.error(`RenderInstance: No renderer for model ${model}`);
|
console.error(`RenderInstance: No renderer for model ${props.model}`);
|
||||||
return <UnknownRenderer model={model} />;
|
return <UnknownRenderer model={props.model} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <RenderComponent instance={instance} />;
|
return <RenderComponent {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -104,7 +107,8 @@ export function RenderInlineModel({
|
|||||||
suffix,
|
suffix,
|
||||||
image,
|
image,
|
||||||
labels,
|
labels,
|
||||||
url
|
url,
|
||||||
|
navigate
|
||||||
}: {
|
}: {
|
||||||
primary: string;
|
primary: string;
|
||||||
secondary?: string;
|
secondary?: string;
|
||||||
@ -112,15 +116,30 @@ export function RenderInlineModel({
|
|||||||
image?: string;
|
image?: string;
|
||||||
labels?: string[];
|
labels?: string[];
|
||||||
url?: string;
|
url?: string;
|
||||||
|
navigate?: any;
|
||||||
}): ReactNode {
|
}): ReactNode {
|
||||||
// TODO: Handle labels
|
// TODO: Handle labels
|
||||||
// TODO: Handle URL
|
|
||||||
|
const onClick = useCallback(
|
||||||
|
(event: any) => {
|
||||||
|
if (url && navigate) {
|
||||||
|
navigateToLink(url, navigate, event);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[url, navigate]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group gap="xs" justify="space-between" wrap="nowrap">
|
<Group gap="xs" justify="space-between" wrap="nowrap">
|
||||||
<Group gap="xs" justify="left" wrap="nowrap">
|
<Group gap="xs" justify="left" wrap="nowrap">
|
||||||
{image && Thumbnail({ src: image, size: 18 })}
|
{image && Thumbnail({ src: image, size: 18 })}
|
||||||
<Text size="sm">{primary}</Text>
|
{url ? (
|
||||||
|
<Anchor href={url} onClick={(event: any) => onClick(event)}>
|
||||||
|
<Text size="sm">{primary}</Text>
|
||||||
|
</Anchor>
|
||||||
|
) : (
|
||||||
|
<Text size="sm">{primary}</Text>
|
||||||
|
)}
|
||||||
{secondary && <Text size="xs">{secondary}</Text>}
|
{secondary && <Text size="xs">{secondary}</Text>}
|
||||||
</Group>
|
</Group>
|
||||||
{suffix && (
|
{suffix && (
|
||||||
@ -144,7 +163,3 @@ export function UnknownRenderer({
|
|||||||
</Alert>
|
</Alert>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InstanceRenderInterface {
|
|
||||||
instance: any;
|
|
||||||
}
|
|
||||||
|
@ -2,20 +2,22 @@ import { t } from '@lingui/macro';
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
|
import { getDetailUrl } from '../../functions/urls';
|
||||||
import { InstanceRenderInterface, RenderInlineModel } from './Instance';
|
import { InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||||
import { StatusRenderer } from './StatusRenderer';
|
import { StatusRenderer } from './StatusRenderer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inline rendering of a single PurchaseOrder instance
|
* Inline rendering of a single PurchaseOrder instance
|
||||||
*/
|
*/
|
||||||
export function RenderPurchaseOrder({
|
export function RenderPurchaseOrder(
|
||||||
instance
|
props: Readonly<InstanceRenderInterface>
|
||||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
): ReactNode {
|
||||||
let supplier = instance.supplier_detail || {};
|
const { instance } = props;
|
||||||
|
const supplier = instance?.supplier_detail || {};
|
||||||
|
|
||||||
// TODO: Handle URL
|
|
||||||
return (
|
return (
|
||||||
<RenderInlineModel
|
<RenderInlineModel
|
||||||
|
{...props}
|
||||||
primary={instance.reference}
|
primary={instance.reference}
|
||||||
secondary={instance.description}
|
secondary={instance.description}
|
||||||
suffix={StatusRenderer({
|
suffix={StatusRenderer({
|
||||||
@ -23,6 +25,11 @@ export function RenderPurchaseOrder({
|
|||||||
type: ModelType.purchaseorder
|
type: ModelType.purchaseorder
|
||||||
})}
|
})}
|
||||||
image={supplier.thumnbnail || supplier.image}
|
image={supplier.thumnbnail || supplier.image}
|
||||||
|
url={
|
||||||
|
props.link
|
||||||
|
? getDetailUrl(ModelType.purchaseorder, instance.pk)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -30,13 +37,15 @@ export function RenderPurchaseOrder({
|
|||||||
/**
|
/**
|
||||||
* Inline rendering of a single ReturnOrder instance
|
* Inline rendering of a single ReturnOrder instance
|
||||||
*/
|
*/
|
||||||
export function RenderReturnOrder({
|
export function RenderReturnOrder(
|
||||||
instance
|
props: Readonly<InstanceRenderInterface>
|
||||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
): ReactNode {
|
||||||
let customer = instance.customer_detail || {};
|
const { instance } = props;
|
||||||
|
const customer = instance?.customer_detail || {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RenderInlineModel
|
<RenderInlineModel
|
||||||
|
{...props}
|
||||||
primary={instance.reference}
|
primary={instance.reference}
|
||||||
secondary={instance.description}
|
secondary={instance.description}
|
||||||
suffix={StatusRenderer({
|
suffix={StatusRenderer({
|
||||||
@ -44,6 +53,11 @@ export function RenderReturnOrder({
|
|||||||
type: ModelType.returnorder
|
type: ModelType.returnorder
|
||||||
})}
|
})}
|
||||||
image={customer.thumnbnail || customer.image}
|
image={customer.thumnbnail || customer.image}
|
||||||
|
url={
|
||||||
|
props.link
|
||||||
|
? getDetailUrl(ModelType.returnorder, instance.pk)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -51,15 +65,15 @@ export function RenderReturnOrder({
|
|||||||
/**
|
/**
|
||||||
* Inline rendering of a single SalesOrder instance
|
* Inline rendering of a single SalesOrder instance
|
||||||
*/
|
*/
|
||||||
export function RenderSalesOrder({
|
export function RenderSalesOrder(
|
||||||
instance
|
props: Readonly<InstanceRenderInterface>
|
||||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
): ReactNode {
|
||||||
let customer = instance.customer_detail || {};
|
const { instance } = props;
|
||||||
|
const customer = instance?.customer_detail || {};
|
||||||
// TODO: Handle URL
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RenderInlineModel
|
<RenderInlineModel
|
||||||
|
{...props}
|
||||||
primary={instance.reference}
|
primary={instance.reference}
|
||||||
secondary={instance.description}
|
secondary={instance.description}
|
||||||
suffix={StatusRenderer({
|
suffix={StatusRenderer({
|
||||||
@ -67,6 +81,9 @@ export function RenderSalesOrder({
|
|||||||
type: ModelType.salesorder
|
type: ModelType.salesorder
|
||||||
})}
|
})}
|
||||||
image={customer.thumnbnail || customer.image}
|
image={customer.thumnbnail || customer.image}
|
||||||
|
url={
|
||||||
|
props.link ? getDetailUrl(ModelType.salesorder, instance.pk) : undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,27 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
import { ModelType } from '../../enums/ModelType';
|
||||||
|
import { getDetailUrl } from '../../functions/urls';
|
||||||
import { InstanceRenderInterface, RenderInlineModel } from './Instance';
|
import { InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inline rendering of a single Part instance
|
* Inline rendering of a single Part instance
|
||||||
*/
|
*/
|
||||||
export function RenderPart({
|
export function RenderPart(
|
||||||
instance
|
props: Readonly<InstanceRenderInterface>
|
||||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
): ReactNode {
|
||||||
|
const { instance } = props;
|
||||||
const stock = t`Stock` + `: ${instance.in_stock}`;
|
const stock = t`Stock` + `: ${instance.in_stock}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RenderInlineModel
|
<RenderInlineModel
|
||||||
|
{...props}
|
||||||
primary={instance.name}
|
primary={instance.name}
|
||||||
secondary={instance.description}
|
secondary={instance.description}
|
||||||
suffix={stock}
|
suffix={stock}
|
||||||
image={instance.thumnbnail || instance.image}
|
image={instance.thumnbnail || instance.image}
|
||||||
|
url={props.link ? getDetailUrl(ModelType.part, instance.pk) : undefined}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -24,17 +29,22 @@ export function RenderPart({
|
|||||||
/**
|
/**
|
||||||
* Inline rendering of a PartCategory instance
|
* Inline rendering of a PartCategory instance
|
||||||
*/
|
*/
|
||||||
export function RenderPartCategory({
|
export function RenderPartCategory(
|
||||||
instance
|
props: Readonly<InstanceRenderInterface>
|
||||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
): ReactNode {
|
||||||
// TODO: Handle URL
|
const { instance } = props;
|
||||||
|
const lvl = '-'.repeat(instance.level || 0);
|
||||||
let lvl = '-'.repeat(instance.level || 0);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RenderInlineModel
|
<RenderInlineModel
|
||||||
|
{...props}
|
||||||
primary={`${lvl} ${instance.name}`}
|
primary={`${lvl} ${instance.name}`}
|
||||||
secondary={instance.description}
|
secondary={instance.description}
|
||||||
|
url={
|
||||||
|
props.link
|
||||||
|
? getDetailUrl(ModelType.partcategory, instance.pk)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,28 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
import { ModelType } from '../../enums/ModelType';
|
||||||
|
import { getDetailUrl } from '../../functions/urls';
|
||||||
import { InstanceRenderInterface, RenderInlineModel } from './Instance';
|
import { InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inline rendering of a single StockLocation instance
|
* Inline rendering of a single StockLocation instance
|
||||||
*/
|
*/
|
||||||
export function RenderStockLocation({
|
export function RenderStockLocation(
|
||||||
instance
|
props: Readonly<InstanceRenderInterface>
|
||||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
): ReactNode {
|
||||||
|
const { instance } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RenderInlineModel
|
<RenderInlineModel
|
||||||
|
{...props}
|
||||||
primary={instance.name}
|
primary={instance.name}
|
||||||
secondary={instance.description}
|
secondary={instance.description}
|
||||||
|
url={
|
||||||
|
props.link
|
||||||
|
? getDetailUrl(ModelType.stocklocation, instance.pk)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -32,9 +42,10 @@ export function RenderStockLocationType({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RenderStockItem({
|
export function RenderStockItem(
|
||||||
instance
|
props: Readonly<InstanceRenderInterface>
|
||||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
): ReactNode {
|
||||||
|
const { instance } = props;
|
||||||
let quantity_string = '';
|
let quantity_string = '';
|
||||||
|
|
||||||
if (instance?.serial !== null && instance?.serial !== undefined) {
|
if (instance?.serial !== null && instance?.serial !== undefined) {
|
||||||
@ -45,9 +56,13 @@ export function RenderStockItem({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<RenderInlineModel
|
<RenderInlineModel
|
||||||
|
{...props}
|
||||||
primary={instance.part_detail?.full_name}
|
primary={instance.part_detail?.full_name}
|
||||||
suffix={quantity_string}
|
suffix={quantity_string}
|
||||||
image={instance.part_detail?.thumbnail || instance.part_detail?.image}
|
image={instance.part_detail?.thumbnail || instance.part_detail?.image}
|
||||||
|
url={
|
||||||
|
props.link ? getDetailUrl(ModelType.stockitem, instance.pk) : undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,7 @@ import { AttachmentTable } from '../../tables/general/AttachmentTable';
|
|||||||
import InstalledItemsTable from '../../tables/stock/InstalledItemsTable';
|
import InstalledItemsTable from '../../tables/stock/InstalledItemsTable';
|
||||||
import { StockItemTable } from '../../tables/stock/StockItemTable';
|
import { StockItemTable } from '../../tables/stock/StockItemTable';
|
||||||
import StockItemTestResultTable from '../../tables/stock/StockItemTestResultTable';
|
import StockItemTestResultTable from '../../tables/stock/StockItemTestResultTable';
|
||||||
|
import { StockTrackingTable } from '../../tables/stock/StockTrackingTable';
|
||||||
|
|
||||||
export default function StockDetail() {
|
export default function StockDetail() {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
@ -269,7 +270,12 @@ export default function StockDetail() {
|
|||||||
{
|
{
|
||||||
name: 'tracking',
|
name: 'tracking',
|
||||||
label: t`Stock Tracking`,
|
label: t`Stock Tracking`,
|
||||||
icon: <IconHistory />
|
icon: <IconHistory />,
|
||||||
|
content: stockitem.pk ? (
|
||||||
|
<StockTrackingTable itemId={stockitem.pk} />
|
||||||
|
) : (
|
||||||
|
<Skeleton />
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'allocations',
|
name: 'allocations',
|
||||||
|
220
src/frontend/src/tables/stock/StockTrackingTable.tsx
Normal file
220
src/frontend/src/tables/stock/StockTrackingTable.tsx
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { Table, Text } from '@mantine/core';
|
||||||
|
import { ReactNode, useCallback, useMemo } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { RenderBuildOrder } from '../../components/render/Build';
|
||||||
|
import { RenderCompany } from '../../components/render/Company';
|
||||||
|
import {
|
||||||
|
RenderPurchaseOrder,
|
||||||
|
RenderReturnOrder,
|
||||||
|
RenderSalesOrder
|
||||||
|
} from '../../components/render/Order';
|
||||||
|
import { RenderPart } from '../../components/render/Part';
|
||||||
|
import { StatusRenderer } from '../../components/render/StatusRenderer';
|
||||||
|
import {
|
||||||
|
RenderStockItem,
|
||||||
|
RenderStockLocation
|
||||||
|
} from '../../components/render/Stock';
|
||||||
|
import { RenderUser } from '../../components/render/User';
|
||||||
|
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||||
|
import { ModelType } from '../../enums/ModelType';
|
||||||
|
import { useTable } from '../../hooks/UseTable';
|
||||||
|
import { apiUrl } from '../../states/ApiState';
|
||||||
|
import { TableColumn } from '../Column';
|
||||||
|
import { DateColumn, DescriptionColumn } from '../ColumnRenderers';
|
||||||
|
import { InvenTreeTable } from '../InvenTreeTable';
|
||||||
|
|
||||||
|
type StockTrackingEntry = {
|
||||||
|
label: string;
|
||||||
|
key: string;
|
||||||
|
details: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function StockTrackingTable({ itemId }: { itemId: number }) {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const table = useTable('stock_tracking');
|
||||||
|
|
||||||
|
// Render "details" for a stock tracking record
|
||||||
|
const renderDetails = useCallback(
|
||||||
|
(record: any) => {
|
||||||
|
const deltas: any = record?.deltas ?? {};
|
||||||
|
|
||||||
|
let entries: StockTrackingEntry[] = [
|
||||||
|
{
|
||||||
|
label: t`Stock Item`,
|
||||||
|
key: 'stockitem',
|
||||||
|
details:
|
||||||
|
deltas.stockitem_detail &&
|
||||||
|
RenderStockItem({ instance: deltas.stockitem_detail })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t`Status`,
|
||||||
|
key: 'status',
|
||||||
|
details:
|
||||||
|
deltas.status &&
|
||||||
|
StatusRenderer({ status: deltas.status, type: ModelType.stockitem })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t`Quantity`,
|
||||||
|
key: 'quantity',
|
||||||
|
details: deltas.quantity
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t`Added`,
|
||||||
|
key: 'added',
|
||||||
|
details: deltas.added
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t`Removed`,
|
||||||
|
key: 'removed',
|
||||||
|
details: deltas.removed
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t`Part`,
|
||||||
|
key: 'part',
|
||||||
|
details:
|
||||||
|
deltas.part_detail &&
|
||||||
|
RenderPart({
|
||||||
|
instance: deltas.part_detail,
|
||||||
|
link: true,
|
||||||
|
navigate: navigate
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t`Location`,
|
||||||
|
key: 'location',
|
||||||
|
details:
|
||||||
|
deltas.location_detail &&
|
||||||
|
RenderStockLocation({
|
||||||
|
instance: deltas.location_detail,
|
||||||
|
link: true,
|
||||||
|
navigate: navigate
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t`Build Order`,
|
||||||
|
key: 'buildorder',
|
||||||
|
details:
|
||||||
|
deltas.buildorder_detail &&
|
||||||
|
RenderBuildOrder({
|
||||||
|
instance: deltas.buildorder_detail,
|
||||||
|
link: true,
|
||||||
|
navigate: navigate
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t`Purchase Order`,
|
||||||
|
key: 'purchaseorder',
|
||||||
|
details:
|
||||||
|
deltas.purchaseorder_detail &&
|
||||||
|
RenderPurchaseOrder({
|
||||||
|
instance: deltas.purchaseorder_detail,
|
||||||
|
link: true,
|
||||||
|
navigate: navigate
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t`Sales Order`,
|
||||||
|
key: 'salesorder',
|
||||||
|
details:
|
||||||
|
deltas.salesorder_detail &&
|
||||||
|
RenderSalesOrder({
|
||||||
|
instance: deltas.salesorder_detail,
|
||||||
|
link: true,
|
||||||
|
navigate: navigate
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t`Return Order`,
|
||||||
|
key: 'returnorder',
|
||||||
|
details:
|
||||||
|
deltas.returnorder_detail &&
|
||||||
|
RenderReturnOrder({
|
||||||
|
instance: deltas.returnorder_detail,
|
||||||
|
link: true,
|
||||||
|
navigate: navigate
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t`Customer`,
|
||||||
|
key: 'customer',
|
||||||
|
details:
|
||||||
|
deltas.customer_detail &&
|
||||||
|
RenderCompany({
|
||||||
|
instance: deltas.customer_detail,
|
||||||
|
link: true,
|
||||||
|
navigate: navigate
|
||||||
|
})
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table striped>
|
||||||
|
<Table.Tbody>
|
||||||
|
{entries.map(
|
||||||
|
(entry) =>
|
||||||
|
entry.details && (
|
||||||
|
<Table.Tr key={entry.key}>
|
||||||
|
<Table.Td>
|
||||||
|
<Text>{entry.label}</Text>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>{entry.details}</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[navigate]
|
||||||
|
);
|
||||||
|
|
||||||
|
const tableColumns: TableColumn[] = useMemo(() => {
|
||||||
|
return [
|
||||||
|
DateColumn({
|
||||||
|
switchable: false
|
||||||
|
}),
|
||||||
|
DescriptionColumn({
|
||||||
|
accessor: 'label'
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
accessor: 'details',
|
||||||
|
title: t`Details`,
|
||||||
|
switchable: false,
|
||||||
|
render: renderDetails
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: 'notes',
|
||||||
|
title: t`Notes`,
|
||||||
|
sortable: false,
|
||||||
|
switchable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: 'user',
|
||||||
|
title: t`User`,
|
||||||
|
render: (record: any) => {
|
||||||
|
if (!record.user_detail) {
|
||||||
|
return <Text size="sm" fs="italic">{t`No user information`}</Text>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return RenderUser({ instance: record.user_detail });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InvenTreeTable
|
||||||
|
tableState={table}
|
||||||
|
url={apiUrl(ApiEndpoints.stock_tracking_list)}
|
||||||
|
columns={tableColumns}
|
||||||
|
props={{
|
||||||
|
params: {
|
||||||
|
item: itemId,
|
||||||
|
user_detail: true
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user