mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-27 01:00:53 +00:00
Refactoring for DetailsTable
This commit is contained in:
@ -1,4 +1,5 @@
|
|||||||
import { Grid, Group, Paper, SimpleGrid } from '@mantine/core';
|
import { Grid, Group, Paper, SimpleGrid } from '@mantine/core';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
import { UserRoles } from '../../enums/Roles';
|
import { UserRoles } from '../../enums/Roles';
|
||||||
import { DetailsField, DetailsTable } from '../../tables/Details';
|
import { DetailsField, DetailsTable } from '../../tables/Details';
|
||||||
@ -23,6 +24,16 @@ export type DetailsImageType = {
|
|||||||
imageActions: DetailImageButtonProps;
|
imageActions: DetailImageButtonProps;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function ItemDetailsGrid(props: React.PropsWithChildren<{}>) {
|
||||||
|
return (
|
||||||
|
<Paper p="xs">
|
||||||
|
<SimpleGrid cols={2} spacing="xs" verticalSpacing="xs">
|
||||||
|
{props.children}
|
||||||
|
</SimpleGrid>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render a Details panel of the given model
|
* Render a Details panel of the given model
|
||||||
* @param params Object with the data of the model to render
|
* @param params Object with the data of the model to render
|
||||||
|
@ -7,7 +7,7 @@ import { ModelType } from '../enums/ModelType';
|
|||||||
export function getDetailUrl(model: ModelType, pk: number | string): string {
|
export function getDetailUrl(model: ModelType, pk: number | string): string {
|
||||||
const modelInfo = ModelInformationDict[model];
|
const modelInfo = ModelInformationDict[model];
|
||||||
|
|
||||||
if (modelInfo && modelInfo.url_detail) {
|
if (!!pk && modelInfo && modelInfo.url_detail) {
|
||||||
return modelInfo.url_detail.replace(':pk', pk.toString());
|
return modelInfo.url_detail.replace(':pk', pk.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,8 @@ import { api } from '../../App';
|
|||||||
import {
|
import {
|
||||||
DetailsImageType,
|
DetailsImageType,
|
||||||
ItemDetailFields,
|
ItemDetailFields,
|
||||||
ItemDetails
|
ItemDetails,
|
||||||
|
ItemDetailsGrid
|
||||||
} from '../../components/details/ItemDetails';
|
} from '../../components/details/ItemDetails';
|
||||||
import {
|
import {
|
||||||
ActionDropdown,
|
ActionDropdown,
|
||||||
@ -56,7 +57,7 @@ import { useEditApiFormModal } from '../../hooks/UseForm';
|
|||||||
import { useInstance } from '../../hooks/UseInstance';
|
import { useInstance } from '../../hooks/UseInstance';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
import { DetailsField } from '../../tables/Details';
|
import { DetailsField, DetailsTable } from '../../tables/Details';
|
||||||
import { BomTable } from '../../tables/bom/BomTable';
|
import { BomTable } from '../../tables/bom/BomTable';
|
||||||
import { UsedInTable } from '../../tables/bom/UsedInTable';
|
import { UsedInTable } from '../../tables/bom/UsedInTable';
|
||||||
import { BuildOrderTable } from '../../tables/build/BuildOrderTable';
|
import { BuildOrderTable } from '../../tables/build/BuildOrderTable';
|
||||||
@ -441,6 +442,192 @@ export default function PartDetail() {
|
|||||||
return fields;
|
return fields;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const detailsPanel = useMemo(() => {
|
||||||
|
if (instanceQuery.isFetching) {
|
||||||
|
return <Skeleton />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct the details tables
|
||||||
|
let tl: DetailsField[] = [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'description',
|
||||||
|
label: t`Description`,
|
||||||
|
copy: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
name: 'variant_of',
|
||||||
|
label: t`Variant of`,
|
||||||
|
model: ModelType.part,
|
||||||
|
hidden: !part.variant_of
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
let tr: DetailsField[] = [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'unallocated_stock',
|
||||||
|
unit: true,
|
||||||
|
label: t`Available Stock`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'total_in_stock',
|
||||||
|
unit: true,
|
||||||
|
label: t`In Stock`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'minimum_stock',
|
||||||
|
unit: true,
|
||||||
|
label: t`Minimum Stock`,
|
||||||
|
hidden: part.minimum_stock <= 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'ordering',
|
||||||
|
label: t`On order`,
|
||||||
|
unit: true,
|
||||||
|
hidden: part.ordering <= 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'progressbar',
|
||||||
|
name: 'allocated_to_build_orders',
|
||||||
|
total: part.required_for_build_orders,
|
||||||
|
progress: part.allocated_to_build_orders,
|
||||||
|
label: t`Allocated to Build Orders`,
|
||||||
|
hidden:
|
||||||
|
!part.assembly ||
|
||||||
|
(part.allocated_to_build_orders <= 0 &&
|
||||||
|
part.required_for_build_orders <= 0)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'progressbar',
|
||||||
|
name: 'allocated_to_sales_orders',
|
||||||
|
total: part.required_for_sales_orders,
|
||||||
|
progress: part.allocated_to_sales_orders,
|
||||||
|
label: t`Allocated to Sales Orders`,
|
||||||
|
hidden:
|
||||||
|
!part.salable ||
|
||||||
|
(part.allocated_to_sales_orders <= 0 &&
|
||||||
|
part.required_for_sales_orders <= 0)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'can_build',
|
||||||
|
unit: true,
|
||||||
|
label: t`Can Build`,
|
||||||
|
hidden: !part.assembly
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'building',
|
||||||
|
unit: true,
|
||||||
|
label: t`Building`,
|
||||||
|
hidden: !part.assembly
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
let bl: DetailsField[] = [
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
name: 'category',
|
||||||
|
label: t`Category`,
|
||||||
|
model: ModelType.partcategory
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'IPN',
|
||||||
|
label: t`IPN`,
|
||||||
|
copy: true,
|
||||||
|
hidden: !part.IPN
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'revision',
|
||||||
|
label: t`Revision`,
|
||||||
|
copy: true,
|
||||||
|
hidden: !part.revision
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'units',
|
||||||
|
label: t`Units`,
|
||||||
|
hidden: !part.units
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'keywords',
|
||||||
|
label: t`Keywords`,
|
||||||
|
copy: true,
|
||||||
|
hidden: !part.keywords
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'responsible',
|
||||||
|
label: t`Responsible`,
|
||||||
|
badge: 'owner',
|
||||||
|
hidden: !part.responsible
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
let br: DetailsField[] = [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'creation_date',
|
||||||
|
label: t`Creation Date`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'creation_user',
|
||||||
|
badge: 'user'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
name: 'default_location',
|
||||||
|
label: t`Default Location`,
|
||||||
|
model: ModelType.stocklocation,
|
||||||
|
hidden: !part.default_location
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
name: 'default_supplier',
|
||||||
|
label: t`Default Supplier`,
|
||||||
|
model: ModelType.supplierpart,
|
||||||
|
hidden: !part.default_supplier
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
name: 'link',
|
||||||
|
label: t`Link`,
|
||||||
|
external: true,
|
||||||
|
copy: true,
|
||||||
|
hidden: !part.link
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ItemDetailsGrid>
|
||||||
|
<DetailsTable fields={tl} item={part} />
|
||||||
|
<DetailsTable fields={tr} item={part} />
|
||||||
|
<DetailsTable fields={bl} item={part} />
|
||||||
|
<DetailsTable fields={br} item={part} />
|
||||||
|
</ItemDetailsGrid>
|
||||||
|
);
|
||||||
|
|
||||||
|
// content: !instanceQuery.isFetching && (
|
||||||
|
// <ItemDetails
|
||||||
|
// appRole={UserRoles.part}
|
||||||
|
// params={part}
|
||||||
|
// apiPath={apiUrl(ApiEndpoints.part_list, part.pk)}
|
||||||
|
// refresh={refreshInstance}
|
||||||
|
// fields={detailFields(part)}
|
||||||
|
// partModel
|
||||||
|
// />
|
||||||
|
// )
|
||||||
|
}, [part, instanceQuery]);
|
||||||
|
|
||||||
// Part data panels (recalculate when part data changes)
|
// Part data panels (recalculate when part data changes)
|
||||||
const partPanels: PanelType[] = useMemo(() => {
|
const partPanels: PanelType[] = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
@ -448,16 +635,7 @@ export default function PartDetail() {
|
|||||||
name: 'details',
|
name: 'details',
|
||||||
label: t`Details`,
|
label: t`Details`,
|
||||||
icon: <IconInfoCircle />,
|
icon: <IconInfoCircle />,
|
||||||
content: !instanceQuery.isFetching && (
|
content: detailsPanel
|
||||||
<ItemDetails
|
|
||||||
appRole={UserRoles.part}
|
|
||||||
params={part}
|
|
||||||
apiPath={apiUrl(ApiEndpoints.part_list, part.pk)}
|
|
||||||
refresh={refreshInstance}
|
|
||||||
fields={detailFields(part)}
|
|
||||||
partModel
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'parameters',
|
name: 'parameters',
|
||||||
|
@ -37,6 +37,7 @@ export type PartIconsType = {
|
|||||||
|
|
||||||
export type DetailsField =
|
export type DetailsField =
|
||||||
| {
|
| {
|
||||||
|
hidden?: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
badge?: BadgeType;
|
badge?: BadgeType;
|
||||||
@ -299,7 +300,7 @@ function TableAnchorValue(props: FieldProps) {
|
|||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const modelDef = getModelInfo(props.field_data.model);
|
const modelDef = getModelInfo(props.field_data.model);
|
||||||
|
|
||||||
if (!modelDef.api_endpoint) {
|
if (!modelDef?.api_endpoint) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,12 +367,12 @@ function CopyField({ value }: { value: string }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TableField({
|
function xTableField({
|
||||||
field_data,
|
field_data,
|
||||||
field_value,
|
field_value,
|
||||||
unit = null
|
unit = null
|
||||||
}: {
|
}: {
|
||||||
field_data: DetailsField[];
|
field_data: DetailsField;
|
||||||
field_value: FieldValueType[];
|
field_value: FieldValueType[];
|
||||||
unit?: string | null;
|
unit?: string | null;
|
||||||
}) {
|
}) {
|
||||||
@ -397,8 +398,8 @@ function TableField({
|
|||||||
justifyContent: 'flex-start'
|
justifyContent: 'flex-start'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<InvenTreeIcon icon={field_data[0].name} />
|
<InvenTreeIcon icon={field_data.name} />
|
||||||
<Text>{field_data[0].label}</Text>
|
<Text>{field_data.label}</Text>
|
||||||
</td>
|
</td>
|
||||||
<td style={{ minWidth: '40%' }}>
|
<td style={{ minWidth: '40%' }}>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
@ -428,52 +429,64 @@ function TableField({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DetailsTable({
|
export function DetailsTableField({
|
||||||
item,
|
item,
|
||||||
fields,
|
field
|
||||||
partIcons = false
|
|
||||||
}: {
|
}: {
|
||||||
item: any;
|
item: any;
|
||||||
fields: DetailsField[][];
|
field: DetailsField;
|
||||||
partIcons?: boolean;
|
}) {
|
||||||
|
function getFieldType(type: string) {
|
||||||
|
switch (type) {
|
||||||
|
case 'text':
|
||||||
|
case 'string':
|
||||||
|
return TableStringValue;
|
||||||
|
case 'link':
|
||||||
|
return TableAnchorValue;
|
||||||
|
case 'progressbar':
|
||||||
|
return ProgressBarValue;
|
||||||
|
default:
|
||||||
|
return TableStringValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const FieldType: any = getFieldType(field.type);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '20px',
|
||||||
|
justifyContent: 'flex-start'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<InvenTreeIcon icon={field.name} />
|
||||||
|
<Text>{field.label}</Text>
|
||||||
|
</td>
|
||||||
|
<td style={{ minWidth: '40%' }}>
|
||||||
|
<FieldType field_data={field} field_value={item[field.name]} />
|
||||||
|
</td>
|
||||||
|
<td>{field.copy && <CopyField value={'hello world'} />}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DetailsTable({
|
||||||
|
item,
|
||||||
|
fields
|
||||||
|
}: {
|
||||||
|
item: any;
|
||||||
|
fields: DetailsField[];
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Paper p="xs" withBorder radius="xs">
|
<Paper p="xs" withBorder radius="xs">
|
||||||
<Table striped>
|
<Table striped>
|
||||||
<tbody>
|
<tbody>
|
||||||
{partIcons && (
|
{fields.map((field: DetailsField, index: number) => (
|
||||||
<tr>
|
<DetailsTableField field={field} item={item} key={index} />
|
||||||
<PartIcons
|
))}
|
||||||
assembly={item.assembly}
|
|
||||||
template={item.is_template}
|
|
||||||
component={item.component}
|
|
||||||
trackable={item.trackable}
|
|
||||||
purchaseable={item.purchaseable}
|
|
||||||
saleable={item.salable}
|
|
||||||
virtual={item.virtual}
|
|
||||||
active={item.active}
|
|
||||||
/>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
{fields.map((data: DetailsField[], index: number) => {
|
|
||||||
let value: FieldValueType[] = [];
|
|
||||||
for (const val of data) {
|
|
||||||
if (val.value_formatter) {
|
|
||||||
value.push(undefined);
|
|
||||||
} else {
|
|
||||||
value.push(item[val.name]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableField
|
|
||||||
field_data={data}
|
|
||||||
field_value={value}
|
|
||||||
key={index}
|
|
||||||
unit={item.units}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
Reference in New Issue
Block a user