mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-01 19:20:55 +00:00
[UI] Enhanced number formatting (#9799)
* Enhanced number formatting * Support part units
This commit is contained in:
@ -24,7 +24,7 @@ import { getDetailUrl } from '@lib/functions/Navigation';
|
|||||||
import { navigateToLink } from '@lib/functions/Navigation';
|
import { navigateToLink } from '@lib/functions/Navigation';
|
||||||
import type { InvenTreeIconType } from '@lib/types/Icons';
|
import type { InvenTreeIconType } from '@lib/types/Icons';
|
||||||
import { useApi } from '../../contexts/ApiContext';
|
import { useApi } from '../../contexts/ApiContext';
|
||||||
import { formatDate } from '../../defaults/formatters';
|
import { formatDate, formatDecimal } from '../../defaults/formatters';
|
||||||
import { InvenTreeIcon } from '../../functions/icons';
|
import { InvenTreeIcon } from '../../functions/icons';
|
||||||
import { useGlobalSettingsState } from '../../states/SettingsState';
|
import { useGlobalSettingsState } from '../../states/SettingsState';
|
||||||
import { CopyButton } from '../buttons/CopyButton';
|
import { CopyButton } from '../buttons/CopyButton';
|
||||||
@ -43,6 +43,7 @@ export type DetailsField = {
|
|||||||
copy?: boolean;
|
copy?: boolean;
|
||||||
value_formatter?: () => ValueFormatterReturn;
|
value_formatter?: () => ValueFormatterReturn;
|
||||||
} & (
|
} & (
|
||||||
|
| NumberDetailField
|
||||||
| StringDetailField
|
| StringDetailField
|
||||||
| BooleanField
|
| BooleanField
|
||||||
| LinkDetailField
|
| LinkDetailField
|
||||||
@ -58,6 +59,11 @@ type StringDetailField = {
|
|||||||
unit?: boolean;
|
unit?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type NumberDetailField = {
|
||||||
|
type: 'number';
|
||||||
|
unit?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
type BooleanField = {
|
type BooleanField = {
|
||||||
type: 'boolean';
|
type: 'boolean';
|
||||||
};
|
};
|
||||||
@ -262,6 +268,27 @@ function DateValue(props: Readonly<FieldProps>) {
|
|||||||
return <Text size='sm'>{formatDate(props.field_value?.toString())}</Text>;
|
return <Text size='sm'>{formatDate(props.field_value?.toString())}</Text>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return a formatted "number" value, with optional unit
|
||||||
|
function NumberValue(props: Readonly<FieldProps>) {
|
||||||
|
const value = props?.field_value;
|
||||||
|
|
||||||
|
// Convert to double
|
||||||
|
const numberValue = Number.parseFloat(value.toString());
|
||||||
|
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return <Text size='sm'>'---'</Text>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group wrap='nowrap' gap='xs' justify='left'>
|
||||||
|
<Text size='sm'>{formatDecimal(numberValue)}</Text>
|
||||||
|
{!!props.field_data?.unit && (
|
||||||
|
<Text size='xs'>[{props.field_data?.unit}]</Text>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the value of a 'string' or 'text' field.
|
* Renders the value of a 'string' or 'text' field.
|
||||||
* If owner is defined, only renders a badge
|
* If owner is defined, only renders a badge
|
||||||
@ -447,6 +474,8 @@ export function DetailsTableField({
|
|||||||
return StatusValue;
|
return StatusValue;
|
||||||
case 'date':
|
case 'date':
|
||||||
return DateValue;
|
return DateValue;
|
||||||
|
case 'number':
|
||||||
|
return NumberValue;
|
||||||
case 'text':
|
case 'text':
|
||||||
case 'string':
|
case 'string':
|
||||||
default:
|
default:
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Progress, Stack, Text } from '@mantine/core';
|
import { Progress, Stack, Text } from '@mantine/core';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { formatDecimal } from '../../defaults/formatters';
|
||||||
|
|
||||||
export type ProgressBarProps = {
|
export type ProgressBarProps = {
|
||||||
value: number;
|
value: number;
|
||||||
@ -30,7 +31,7 @@ export function ProgressBar(props: Readonly<ProgressBarProps>) {
|
|||||||
<Stack gap={2} style={{ flexGrow: 1, minWidth: '100px' }}>
|
<Stack gap={2} style={{ flexGrow: 1, minWidth: '100px' }}>
|
||||||
{props.progressLabel && (
|
{props.progressLabel && (
|
||||||
<Text ta='center' size='xs'>
|
<Text ta='center' size='xs'>
|
||||||
{props.value} / {props.maximum}
|
{formatDecimal(props.value)} / {formatDecimal(props.maximum)}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
<Progress
|
<Progress
|
||||||
|
@ -249,44 +249,45 @@ export default function PartDetail() {
|
|||||||
// Top right - stock availability information
|
// Top right - stock availability information
|
||||||
const tr: DetailsField[] = [
|
const tr: DetailsField[] = [
|
||||||
{
|
{
|
||||||
type: 'string',
|
type: 'number',
|
||||||
name: 'total_in_stock',
|
name: 'total_in_stock',
|
||||||
unit: true,
|
unit: part.units,
|
||||||
label: t`In Stock`
|
label: t`In Stock`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'string',
|
type: 'number',
|
||||||
name: 'unallocated_stock',
|
name: 'unallocated_stock',
|
||||||
unit: true,
|
unit: part.units,
|
||||||
label: t`Available Stock`,
|
label: t`Available Stock`,
|
||||||
hidden: part.total_in_stock == part.unallocated_stock
|
hidden: part.total_in_stock == part.unallocated_stock
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'string',
|
type: 'number',
|
||||||
name: 'variant_stock',
|
name: 'variant_stock',
|
||||||
unit: true,
|
unit: part.units,
|
||||||
label: t`Variant Stock`,
|
label: t`Variant Stock`,
|
||||||
hidden: !part.variant_stock,
|
hidden: !part.variant_stock,
|
||||||
icon: 'stock'
|
icon: 'stock'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'string',
|
type: 'number',
|
||||||
name: 'minimum_stock',
|
name: 'minimum_stock',
|
||||||
unit: true,
|
unit: part.units,
|
||||||
label: t`Minimum Stock`,
|
label: t`Minimum Stock`,
|
||||||
hidden: part.minimum_stock <= 0
|
hidden: part.minimum_stock <= 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'string',
|
type: 'number',
|
||||||
name: 'ordering',
|
name: 'ordering',
|
||||||
label: t`On order`,
|
label: t`On order`,
|
||||||
unit: true,
|
unit: part.units,
|
||||||
hidden: !part.purchaseable || part.ordering <= 0
|
hidden: !part.purchaseable || part.ordering <= 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'string',
|
type: 'number',
|
||||||
name: 'required',
|
name: 'required',
|
||||||
label: t`Required for Orders`,
|
label: t`Required for Orders`,
|
||||||
|
unit: part.units,
|
||||||
hidden: part.required <= 0,
|
hidden: part.required <= 0,
|
||||||
icon: 'stocktake'
|
icon: 'stocktake'
|
||||||
},
|
},
|
||||||
@ -315,7 +316,7 @@ export default function PartDetail() {
|
|||||||
part.allocated_to_sales_orders <= 0)
|
part.allocated_to_sales_orders <= 0)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'string',
|
type: 'number',
|
||||||
name: 'can_build',
|
name: 'can_build',
|
||||||
unit: true,
|
unit: true,
|
||||||
label: t`Can Build`,
|
label: t`Can Build`,
|
||||||
|
@ -255,21 +255,24 @@ export default function StockDetail() {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'number',
|
||||||
name: 'quantity',
|
name: 'quantity',
|
||||||
label: t`Quantity`,
|
label: t`Quantity`,
|
||||||
|
unit: part?.units,
|
||||||
hidden: !!stockitem.serial && stockitem.quantity == 1
|
hidden: !!stockitem.serial && stockitem.quantity == 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'number',
|
||||||
name: 'available_stock',
|
name: 'available_stock',
|
||||||
label: t`Available`,
|
label: t`Available`,
|
||||||
|
unit: part?.units,
|
||||||
icon: 'stock'
|
icon: 'stock'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'number',
|
||||||
name: 'allocated',
|
name: 'allocated',
|
||||||
label: t`Allocated to Orders`,
|
label: t`Allocated to Orders`,
|
||||||
|
unit: part?.units,
|
||||||
icon: 'tick_off',
|
icon: 'tick_off',
|
||||||
hidden: !stockitem.allocated
|
hidden: !stockitem.allocated
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user