mirror of
https://github.com/inventree/InvenTree.git
synced 2025-08-13 07:10:53 +00:00
Merge branch 'master' of https://github.com/inventree/InvenTree into pui-maintine-v7
This commit is contained in:
@@ -90,7 +90,7 @@ function detect_envs() {
|
|||||||
echo "# Using existing config file: ${INVENTREE_CONFIG_FILE}"
|
echo "# Using existing config file: ${INVENTREE_CONFIG_FILE}"
|
||||||
|
|
||||||
# Install parser
|
# Install parser
|
||||||
pip install -r ${APP_HOME}/.github/requirements.txt -q
|
pip install --require-hashes -r ${APP_HOME}/.github/requirements.txt -q
|
||||||
|
|
||||||
# Load config
|
# Load config
|
||||||
local CONF=$(cat ${INVENTREE_CONFIG_FILE} | jc --yaml)
|
local CONF=$(cat ${INVENTREE_CONFIG_FILE} | jc --yaml)
|
||||||
|
@@ -1,11 +1,14 @@
|
|||||||
"""InvenTree API version information."""
|
"""InvenTree API version information."""
|
||||||
|
|
||||||
# InvenTree API version
|
# InvenTree API version
|
||||||
INVENTREE_API_VERSION = 192
|
INVENTREE_API_VERSION = 193
|
||||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||||
|
|
||||||
INVENTREE_API_TEXT = """
|
INVENTREE_API_TEXT = """
|
||||||
|
|
||||||
|
v193 - 2024-04-30 : https://github.com/inventree/InvenTree/pull/7144
|
||||||
|
- Adds "assigned_to" filter to PurchaseOrder / SalesOrder / ReturnOrder API endpoints
|
||||||
|
|
||||||
v192 - 2024-04-23 : https://github.com/inventree/InvenTree/pull/7106
|
v192 - 2024-04-23 : https://github.com/inventree/InvenTree/pull/7106
|
||||||
- Adds 'trackable' ordering option to BuildLineLabel API endpoint
|
- Adds 'trackable' ordering option to BuildLineLabel API endpoint
|
||||||
|
|
||||||
|
@@ -148,6 +148,10 @@ class OrderFilter(rest_filters.FilterSet):
|
|||||||
return queryset.exclude(project_code=None)
|
return queryset.exclude(project_code=None)
|
||||||
return queryset.filter(project_code=None)
|
return queryset.filter(project_code=None)
|
||||||
|
|
||||||
|
assigned_to = rest_filters.ModelChoiceFilter(
|
||||||
|
queryset=Owner.objects.all(), field_name='responsible'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LineItemFilter(rest_filters.FilterSet):
|
class LineItemFilter(rest_filters.FilterSet):
|
||||||
"""Base class for custom API filters for order line item list(s)."""
|
"""Base class for custom API filters for order line item list(s)."""
|
||||||
|
@@ -77,16 +77,18 @@ class AbstractOrderSerializer(serializers.Serializer):
|
|||||||
"""Abstract serializer class which provides fields common to all order types."""
|
"""Abstract serializer class which provides fields common to all order types."""
|
||||||
|
|
||||||
# Number of line items in this order
|
# Number of line items in this order
|
||||||
line_items = serializers.IntegerField(read_only=True)
|
line_items = serializers.IntegerField(read_only=True, label=_('Line Items'))
|
||||||
|
|
||||||
# Number of completed line items (this is an annotated field)
|
# Number of completed line items (this is an annotated field)
|
||||||
completed_lines = serializers.IntegerField(read_only=True)
|
completed_lines = serializers.IntegerField(
|
||||||
|
read_only=True, label=_('Completed Lines')
|
||||||
|
)
|
||||||
|
|
||||||
# Human-readable status text (read-only)
|
# Human-readable status text (read-only)
|
||||||
status_text = serializers.CharField(source='get_status_display', read_only=True)
|
status_text = serializers.CharField(source='get_status_display', read_only=True)
|
||||||
|
|
||||||
# status field cannot be set directly
|
# status field cannot be set directly
|
||||||
status = serializers.IntegerField(read_only=True)
|
status = serializers.IntegerField(read_only=True, label=_('Order Status'))
|
||||||
|
|
||||||
# Reference string is *required*
|
# Reference string is *required*
|
||||||
reference = serializers.CharField(required=True)
|
reference = serializers.CharField(required=True)
|
||||||
@@ -114,7 +116,9 @@ class AbstractOrderSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
barcode_hash = serializers.CharField(read_only=True)
|
barcode_hash = serializers.CharField(read_only=True)
|
||||||
|
|
||||||
creation_date = serializers.DateField(required=False, allow_null=True)
|
creation_date = serializers.DateField(
|
||||||
|
required=False, allow_null=True, label=_('Creation Date')
|
||||||
|
)
|
||||||
|
|
||||||
def validate_reference(self, reference):
|
def validate_reference(self, reference):
|
||||||
"""Custom validation for the reference field."""
|
"""Custom validation for the reference field."""
|
||||||
|
@@ -5,6 +5,6 @@ import { ActionButton, ActionButtonProps } from './ActionButton';
|
|||||||
/**
|
/**
|
||||||
* A generic icon button which is used to add or create a new item
|
* A generic icon button which is used to add or create a new item
|
||||||
*/
|
*/
|
||||||
export function AddItemButton(props: ActionButtonProps) {
|
export function AddItemButton(props: Readonly<ActionButtonProps>) {
|
||||||
return <ActionButton {...props} color="green" icon={<IconPlus />} />;
|
return <ActionButton {...props} color="green" icon={<IconPlus />} />;
|
||||||
}
|
}
|
||||||
|
@@ -36,7 +36,7 @@ export function SplitButton({
|
|||||||
selected,
|
selected,
|
||||||
setSelected,
|
setSelected,
|
||||||
loading
|
loading
|
||||||
}: SplitButtonProps) {
|
}: Readonly<SplitButtonProps>) {
|
||||||
const [current, setCurrent] = useState<string>(defaultSelected);
|
const [current, setCurrent] = useState<string>(defaultSelected);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@@ -180,7 +180,7 @@ function NameBadge({ pk, type }: { pk: string | number; type: BadgeType }) {
|
|||||||
* If owner is defined, only renders a badge
|
* If owner is defined, only renders a badge
|
||||||
* If user is defined, a badge is rendered in addition to main value
|
* If user is defined, a badge is rendered in addition to main value
|
||||||
*/
|
*/
|
||||||
function TableStringValue(props: FieldProps) {
|
function TableStringValue(props: Readonly<FieldProps>) {
|
||||||
let value = props?.field_value;
|
let value = props?.field_value;
|
||||||
|
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
@@ -217,11 +217,11 @@ function TableStringValue(props: FieldProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BooleanValue(props: FieldProps) {
|
function BooleanValue(props: Readonly<FieldProps>) {
|
||||||
return <YesNoButton value={props.field_value} />;
|
return <YesNoButton value={props.field_value} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function TableAnchorValue(props: FieldProps) {
|
function TableAnchorValue(props: Readonly<FieldProps>) {
|
||||||
if (props.field_data.external) {
|
if (props.field_data.external) {
|
||||||
return (
|
return (
|
||||||
<Anchor
|
<Anchor
|
||||||
@@ -303,7 +303,7 @@ function TableAnchorValue(props: FieldProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProgressBarValue(props: FieldProps) {
|
function ProgressBarValue(props: Readonly<FieldProps>) {
|
||||||
return (
|
return (
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
value={props.field_data.progress}
|
value={props.field_data.progress}
|
||||||
@@ -313,7 +313,7 @@ function ProgressBarValue(props: FieldProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function StatusValue(props: FieldProps) {
|
function StatusValue(props: Readonly<FieldProps>) {
|
||||||
return (
|
return (
|
||||||
<StatusRenderer type={props.field_data.model} status={props.field_value} />
|
<StatusRenderer type={props.field_data.model} status={props.field_value} />
|
||||||
);
|
);
|
||||||
|
@@ -7,7 +7,7 @@ export type DetailsBadgeProps = {
|
|||||||
visible?: boolean;
|
visible?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function DetailsBadge(props: DetailsBadgeProps) {
|
export default function DetailsBadge(props: Readonly<DetailsBadgeProps>) {
|
||||||
if (props.visible == false) {
|
if (props.visible == false) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@@ -322,7 +322,7 @@ function ImageActionButtons({
|
|||||||
/**
|
/**
|
||||||
* Renders an image with action buttons for display on Details panels
|
* Renders an image with action buttons for display on Details panels
|
||||||
*/
|
*/
|
||||||
export function DetailsImage(props: DetailImageProps) {
|
export function DetailsImage(props: Readonly<DetailImageProps>) {
|
||||||
// Displays a group of ActionButtons on hover
|
// Displays a group of ActionButtons on hover
|
||||||
const { hovered, ref } = useHover();
|
const { hovered, ref } = useHover();
|
||||||
const [img, setImg] = useState<string>(props.src ?? backup_image);
|
const [img, setImg] = useState<string>(props.src ?? backup_image);
|
||||||
|
@@ -87,7 +87,7 @@ type TemplateEditorProps = {
|
|||||||
template: TemplateI;
|
template: TemplateI;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function TemplateEditor(props: TemplateEditorProps) {
|
export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
|
||||||
const { downloadUrl, editors, previewAreas, preview } = props;
|
const { downloadUrl, editors, previewAreas, preview } = props;
|
||||||
const editorRef = useRef<EditorRef>();
|
const editorRef = useRef<EditorRef>();
|
||||||
const previewRef = useRef<PreviewAreaRef>();
|
const previewRef = useRef<PreviewAreaRef>();
|
||||||
|
@@ -6,7 +6,12 @@ interface DocInfoProps extends BaseDocProps {
|
|||||||
size?: number;
|
size?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DocInfo({ size = 18, text, detail, link }: DocInfoProps) {
|
export function DocInfo({
|
||||||
|
size = 18,
|
||||||
|
text,
|
||||||
|
detail,
|
||||||
|
link
|
||||||
|
}: Readonly<DocInfoProps>) {
|
||||||
return (
|
return (
|
||||||
<DocTooltip text={text} detail={detail} link={link}>
|
<DocTooltip text={text} detail={detail} link={link}>
|
||||||
<IconInfoCircle size={size} />
|
<IconInfoCircle size={size} />
|
||||||
|
@@ -21,7 +21,7 @@ export function DocTooltip({
|
|||||||
detail,
|
detail,
|
||||||
link,
|
link,
|
||||||
docchildren
|
docchildren
|
||||||
}: DocTooltipProps) {
|
}: Readonly<DocTooltipProps>) {
|
||||||
return (
|
return (
|
||||||
<HoverCard
|
<HoverCard
|
||||||
shadow="md"
|
shadow="md"
|
||||||
|
@@ -6,13 +6,14 @@ export type ProgressBarProps = {
|
|||||||
maximum?: number;
|
maximum?: number;
|
||||||
label?: string;
|
label?: string;
|
||||||
progressLabel?: boolean;
|
progressLabel?: boolean;
|
||||||
|
size?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A progress bar element, built on mantine.Progress
|
* A progress bar element, built on mantine.Progress
|
||||||
* The color of the bar is determined based on the value
|
* The color of the bar is determined based on the value
|
||||||
*/
|
*/
|
||||||
export function ProgressBar(props: ProgressBarProps) {
|
export function ProgressBar(props: Readonly<ProgressBarProps>) {
|
||||||
const progress = useMemo(() => {
|
const progress = useMemo(() => {
|
||||||
let maximum = props.maximum ?? 100;
|
let maximum = props.maximum ?? 100;
|
||||||
let value = Math.max(props.value, 0);
|
let value = Math.max(props.value, 0);
|
||||||
@@ -31,8 +32,8 @@ export function ProgressBar(props: ProgressBarProps) {
|
|||||||
<Progress
|
<Progress
|
||||||
value={progress}
|
value={progress}
|
||||||
color={progress < 100 ? 'orange' : progress > 100 ? 'blue' : 'green'}
|
color={progress < 100 ? 'orange' : progress > 100 ? 'blue' : 'green'}
|
||||||
size="sm"
|
size={props.size ?? 'md'}
|
||||||
radius="xs"
|
radius="sm"
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
@@ -14,7 +14,7 @@ export function TitleWithDoc({
|
|||||||
size,
|
size,
|
||||||
text,
|
text,
|
||||||
detail
|
detail
|
||||||
}: DocTitleProps) {
|
}: Readonly<DocTitleProps>) {
|
||||||
return (
|
return (
|
||||||
<Group>
|
<Group>
|
||||||
<Title variant={variant} order={order} size={size}>
|
<Title variant={variant} order={order} size={size}>
|
||||||
|
@@ -29,7 +29,7 @@ function DetailDrawerComponent({
|
|||||||
size,
|
size,
|
||||||
closeOnEscape = true,
|
closeOnEscape = true,
|
||||||
renderContent
|
renderContent
|
||||||
}: DrawerProps) {
|
}: Readonly<DrawerProps>) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ function DetailDrawerComponent({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DetailDrawer(props: DrawerProps) {
|
export function DetailDrawer(props: Readonly<DrawerProps>) {
|
||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path=":id?/" element={<DetailDrawerComponent {...props} />} />
|
<Route path=":id?/" element={<DetailDrawerComponent {...props} />} />
|
||||||
|
@@ -5,6 +5,17 @@ import { ApiImage } from '../images/ApiImage';
|
|||||||
import { StylishText } from '../items/StylishText';
|
import { StylishText } from '../items/StylishText';
|
||||||
import { Breadcrumb, BreadcrumbList } from './BreadcrumbList';
|
import { Breadcrumb, BreadcrumbList } from './BreadcrumbList';
|
||||||
|
|
||||||
|
interface PageDetailInterface {
|
||||||
|
title?: string;
|
||||||
|
subtitle?: string;
|
||||||
|
imageUrl?: string;
|
||||||
|
detail?: ReactNode;
|
||||||
|
badges?: ReactNode[];
|
||||||
|
breadcrumbs?: Breadcrumb[];
|
||||||
|
breadcrumbAction?: () => void;
|
||||||
|
actions?: ReactNode[];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a "standard" page detail for common display between pages.
|
* Construct a "standard" page detail for common display between pages.
|
||||||
*
|
*
|
||||||
@@ -20,16 +31,7 @@ export function PageDetail({
|
|||||||
breadcrumbs,
|
breadcrumbs,
|
||||||
breadcrumbAction,
|
breadcrumbAction,
|
||||||
actions
|
actions
|
||||||
}: {
|
}: Readonly<PageDetailInterface>) {
|
||||||
title?: string;
|
|
||||||
subtitle?: string;
|
|
||||||
imageUrl?: string;
|
|
||||||
detail?: ReactNode;
|
|
||||||
badges?: ReactNode[];
|
|
||||||
breadcrumbs?: Breadcrumb[];
|
|
||||||
breadcrumbAction?: () => void;
|
|
||||||
actions?: ReactNode[];
|
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
{breadcrumbs && breadcrumbs.length > 0 && (
|
{breadcrumbs && breadcrumbs.length > 0 && (
|
||||||
|
@@ -50,7 +50,7 @@ function BasePanelGroup({
|
|||||||
onPanelChange,
|
onPanelChange,
|
||||||
selectedPanel,
|
selectedPanel,
|
||||||
collapsible = true
|
collapsible = true
|
||||||
}: PanelProps): ReactNode {
|
}: Readonly<PanelProps>): ReactNode {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { panel } = useParams();
|
const { panel } = useParams();
|
||||||
|
|
||||||
@@ -178,7 +178,11 @@ function BasePanelGroup({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function IndexPanelComponent({ pageKey, selectedPanel, panels }: PanelProps) {
|
function IndexPanelComponent({
|
||||||
|
pageKey,
|
||||||
|
selectedPanel,
|
||||||
|
panels
|
||||||
|
}: Readonly<PanelProps>) {
|
||||||
const lastUsedPanel = useLocalState((state) => {
|
const lastUsedPanel = useLocalState((state) => {
|
||||||
const panelName =
|
const panelName =
|
||||||
selectedPanel || state.lastUsedPanels[pageKey] || panels[0]?.name;
|
selectedPanel || state.lastUsedPanels[pageKey] || panels[0]?.name;
|
||||||
@@ -203,7 +207,7 @@ function IndexPanelComponent({ pageKey, selectedPanel, panels }: PanelProps) {
|
|||||||
* @param onPanelChange - Callback when the active panel changes
|
* @param onPanelChange - Callback when the active panel changes
|
||||||
* @param collapsible - If true, the panel group can be collapsed (defaults to true)
|
* @param collapsible - If true, the panel group can be collapsed (defaults to true)
|
||||||
*/
|
*/
|
||||||
export function PanelGroup(props: PanelProps) {
|
export function PanelGroup(props: Readonly<PanelProps>) {
|
||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route index element={<IndexPanelComponent {...props} />} />
|
<Route index element={<IndexPanelComponent {...props} />} />
|
||||||
|
@@ -3,6 +3,15 @@ import { IconSwitch } from '@tabler/icons-react';
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
interface SettingsHeaderInterface {
|
||||||
|
title: string | ReactNode;
|
||||||
|
shorthand?: string;
|
||||||
|
subtitle?: string | ReactNode;
|
||||||
|
switch_condition?: boolean;
|
||||||
|
switch_text?: string | ReactNode;
|
||||||
|
switch_link?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a settings page header with interlinks to one other settings page
|
* Construct a settings page header with interlinks to one other settings page
|
||||||
*/
|
*/
|
||||||
@@ -13,14 +22,7 @@ export function SettingsHeader({
|
|||||||
switch_condition = true,
|
switch_condition = true,
|
||||||
switch_text,
|
switch_text,
|
||||||
switch_link
|
switch_link
|
||||||
}: {
|
}: Readonly<SettingsHeaderInterface>) {
|
||||||
title: string | ReactNode;
|
|
||||||
shorthand?: string;
|
|
||||||
subtitle?: string | ReactNode;
|
|
||||||
switch_condition?: boolean;
|
|
||||||
switch_text?: string | ReactNode;
|
|
||||||
switch_link?: string;
|
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="0" ml={'sm'}>
|
<Stack gap="0" ml={'sm'}>
|
||||||
<Group>
|
<Group>
|
||||||
|
@@ -1,13 +1,15 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { 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({ instance }: { instance: any }): ReactNode {
|
export function RenderBuildOrder({
|
||||||
|
instance
|
||||||
|
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||||
return (
|
return (
|
||||||
<RenderInlineModel
|
<RenderInlineModel
|
||||||
primary={instance.reference}
|
primary={instance.reference}
|
||||||
@@ -24,7 +26,9 @@ export function RenderBuildOrder({ instance }: { instance: any }): ReactNode {
|
|||||||
/*
|
/*
|
||||||
* Inline rendering of a single BuildLine instance
|
* Inline rendering of a single BuildLine instance
|
||||||
*/
|
*/
|
||||||
export function RenderBuildLine({ instance }: { instance: any }): ReactNode {
|
export function RenderBuildLine({
|
||||||
|
instance
|
||||||
|
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||||
return (
|
return (
|
||||||
<RenderInlineModel
|
<RenderInlineModel
|
||||||
primary={instance.part_detail.full_name}
|
primary={instance.part_detail.full_name}
|
||||||
|
@@ -1,11 +1,13 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
import { RenderInlineModel } from './Instance';
|
import { InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inline rendering of a single Address instance
|
* Inline rendering of a single Address instance
|
||||||
*/
|
*/
|
||||||
export function RenderAddress({ instance }: { instance: any }): ReactNode {
|
export function RenderAddress({
|
||||||
|
instance
|
||||||
|
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||||
let text = [
|
let text = [
|
||||||
instance.country,
|
instance.country,
|
||||||
instance.postal_code,
|
instance.postal_code,
|
||||||
@@ -23,7 +25,9 @@ export function RenderAddress({ instance }: { instance: any }): ReactNode {
|
|||||||
/**
|
/**
|
||||||
* Inline rendering of a single Company instance
|
* Inline rendering of a single Company instance
|
||||||
*/
|
*/
|
||||||
export function RenderCompany({ instance }: { instance: any }): ReactNode {
|
export function RenderCompany({
|
||||||
|
instance
|
||||||
|
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||||
// TODO: Handle URL
|
// TODO: Handle URL
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -38,14 +42,18 @@ export function RenderCompany({ instance }: { instance: any }): ReactNode {
|
|||||||
/**
|
/**
|
||||||
* Inline rendering of a single Contact instance
|
* Inline rendering of a single Contact instance
|
||||||
*/
|
*/
|
||||||
export function RenderContact({ instance }: { instance: any }): ReactNode {
|
export function RenderContact({
|
||||||
|
instance
|
||||||
|
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||||
return <RenderInlineModel primary={instance.name} />;
|
return <RenderInlineModel primary={instance.name} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inline rendering of a single SupplierPart instance
|
* Inline rendering of a single SupplierPart instance
|
||||||
*/
|
*/
|
||||||
export function RenderSupplierPart({ instance }: { instance: any }): ReactNode {
|
export function RenderSupplierPart({
|
||||||
|
instance
|
||||||
|
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||||
// TODO: handle URL
|
// TODO: handle URL
|
||||||
|
|
||||||
let supplier = instance.supplier_detail ?? {};
|
let supplier = instance.supplier_detail ?? {};
|
||||||
@@ -66,9 +74,7 @@ export function RenderSupplierPart({ instance }: { instance: any }): ReactNode {
|
|||||||
*/
|
*/
|
||||||
export function RenderManufacturerPart({
|
export function RenderManufacturerPart({
|
||||||
instance
|
instance
|
||||||
}: {
|
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||||
instance: any;
|
|
||||||
}): ReactNode {
|
|
||||||
let part = instance.part_detail ?? {};
|
let part = instance.part_detail ?? {};
|
||||||
let manufacturer = instance.manufacturer_detail ?? {};
|
let manufacturer = instance.manufacturer_detail ?? {};
|
||||||
|
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
import { RenderInlineModel } from './Instance';
|
import { InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||||
|
|
||||||
export function RenderProjectCode({ instance }: { instance: any }): ReactNode {
|
export function RenderProjectCode({
|
||||||
|
instance
|
||||||
|
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||||
return (
|
return (
|
||||||
instance && (
|
instance && (
|
||||||
<RenderInlineModel
|
<RenderInlineModel
|
||||||
|
@@ -37,7 +37,7 @@ type EnumDictionary<T extends string | symbol | number, U> = {
|
|||||||
*/
|
*/
|
||||||
const RendererLookup: EnumDictionary<
|
const RendererLookup: EnumDictionary<
|
||||||
ModelType,
|
ModelType,
|
||||||
(props: { instance: any }) => ReactNode
|
(props: Readonly<InstanceRenderInterface>) => ReactNode
|
||||||
> = {
|
> = {
|
||||||
[ModelType.address]: RenderAddress,
|
[ModelType.address]: RenderAddress,
|
||||||
[ModelType.build]: RenderBuildOrder,
|
[ModelType.build]: RenderBuildOrder,
|
||||||
@@ -139,3 +139,7 @@ export function UnknownRenderer({
|
|||||||
</Alert>
|
</Alert>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface InstanceRenderInterface {
|
||||||
|
instance: any;
|
||||||
|
}
|
||||||
|
@@ -2,7 +2,7 @@ import { t } from '@lingui/macro';
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { RenderInlineModel } from './Instance';
|
import { InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||||
import { StatusRenderer } from './StatusRenderer';
|
import { StatusRenderer } from './StatusRenderer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -10,9 +10,7 @@ import { StatusRenderer } from './StatusRenderer';
|
|||||||
*/
|
*/
|
||||||
export function RenderPurchaseOrder({
|
export function RenderPurchaseOrder({
|
||||||
instance
|
instance
|
||||||
}: {
|
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||||
instance: any;
|
|
||||||
}): ReactNode {
|
|
||||||
let supplier = instance.supplier_detail || {};
|
let supplier = instance.supplier_detail || {};
|
||||||
|
|
||||||
// TODO: Handle URL
|
// TODO: Handle URL
|
||||||
@@ -32,7 +30,9 @@ export function RenderPurchaseOrder({
|
|||||||
/**
|
/**
|
||||||
* Inline rendering of a single ReturnOrder instance
|
* Inline rendering of a single ReturnOrder instance
|
||||||
*/
|
*/
|
||||||
export function RenderReturnOrder({ instance }: { instance: any }): ReactNode {
|
export function RenderReturnOrder({
|
||||||
|
instance
|
||||||
|
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||||
let customer = instance.customer_detail || {};
|
let customer = instance.customer_detail || {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -51,7 +51,9 @@ export function RenderReturnOrder({ instance }: { instance: any }): ReactNode {
|
|||||||
/**
|
/**
|
||||||
* Inline rendering of a single SalesOrder instance
|
* Inline rendering of a single SalesOrder instance
|
||||||
*/
|
*/
|
||||||
export function RenderSalesOrder({ instance }: { instance: any }): ReactNode {
|
export function RenderSalesOrder({
|
||||||
|
instance
|
||||||
|
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||||
let customer = instance.customer_detail || {};
|
let customer = instance.customer_detail || {};
|
||||||
|
|
||||||
// TODO: Handle URL
|
// TODO: Handle URL
|
||||||
|
@@ -1,12 +1,14 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
import { 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({ instance }: { instance: any }): ReactNode {
|
export function RenderPart({
|
||||||
|
instance
|
||||||
|
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||||
const stock = t`Stock` + `: ${instance.in_stock}`;
|
const stock = t`Stock` + `: ${instance.in_stock}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -22,7 +24,9 @@ export function RenderPart({ instance }: { instance: any }): ReactNode {
|
|||||||
/**
|
/**
|
||||||
* Inline rendering of a PartCategory instance
|
* Inline rendering of a PartCategory instance
|
||||||
*/
|
*/
|
||||||
export function RenderPartCategory({ instance }: { instance: any }): ReactNode {
|
export function RenderPartCategory({
|
||||||
|
instance
|
||||||
|
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||||
// TODO: Handle URL
|
// TODO: Handle URL
|
||||||
|
|
||||||
let lvl = '-'.repeat(instance.level || 0);
|
let lvl = '-'.repeat(instance.level || 0);
|
||||||
|
@@ -14,7 +14,7 @@ export interface StatusCodeListInterface {
|
|||||||
[key: string]: StatusCodeInterface;
|
[key: string]: StatusCodeInterface;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface renderStatusLabelOptionsInterface {
|
interface RenderStatusLabelOptionsInterface {
|
||||||
size?: MantineSize;
|
size?: MantineSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ interface renderStatusLabelOptionsInterface {
|
|||||||
function renderStatusLabel(
|
function renderStatusLabel(
|
||||||
key: string | number,
|
key: string | number,
|
||||||
codes: StatusCodeListInterface,
|
codes: StatusCodeListInterface,
|
||||||
options: renderStatusLabelOptionsInterface = {}
|
options: RenderStatusLabelOptionsInterface = {}
|
||||||
) {
|
) {
|
||||||
let text = null;
|
let text = null;
|
||||||
let color = null;
|
let color = null;
|
||||||
@@ -70,7 +70,7 @@ export const StatusRenderer = ({
|
|||||||
}: {
|
}: {
|
||||||
status: string | number;
|
status: string | number;
|
||||||
type: ModelType | string;
|
type: ModelType | string;
|
||||||
options?: renderStatusLabelOptionsInterface;
|
options?: RenderStatusLabelOptionsInterface;
|
||||||
}) => {
|
}) => {
|
||||||
const statusCodeList = useGlobalStatusState.getState().status;
|
const statusCodeList = useGlobalStatusState.getState().status;
|
||||||
|
|
||||||
|
@@ -1,16 +1,14 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
import { 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
|
instance
|
||||||
}: {
|
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||||
instance: any;
|
|
||||||
}): ReactNode {
|
|
||||||
return (
|
return (
|
||||||
<RenderInlineModel
|
<RenderInlineModel
|
||||||
primary={instance.name}
|
primary={instance.name}
|
||||||
@@ -19,7 +17,9 @@ export function RenderStockLocation({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RenderStockItem({ instance }: { instance: any }): ReactNode {
|
export function RenderStockItem({
|
||||||
|
instance
|
||||||
|
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||||
let quantity_string = '';
|
let quantity_string = '';
|
||||||
|
|
||||||
if (instance?.serial !== null && instance?.serial !== undefined) {
|
if (instance?.serial !== null && instance?.serial !== undefined) {
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
import { IconUser, IconUsersGroup } from '@tabler/icons-react';
|
import { IconUser, IconUsersGroup } from '@tabler/icons-react';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
import { RenderInlineModel } from './Instance';
|
import { InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||||
|
|
||||||
export function RenderOwner({ instance }: { instance: any }): ReactNode {
|
export function RenderOwner({
|
||||||
|
instance
|
||||||
|
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||||
return (
|
return (
|
||||||
instance && (
|
instance && (
|
||||||
<RenderInlineModel
|
<RenderInlineModel
|
||||||
@@ -14,7 +16,9 @@ export function RenderOwner({ instance }: { instance: any }): ReactNode {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RenderUser({ instance }: { instance: any }): ReactNode {
|
export function RenderUser({
|
||||||
|
instance
|
||||||
|
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||||
return (
|
return (
|
||||||
instance && (
|
instance && (
|
||||||
<RenderInlineModel
|
<RenderInlineModel
|
||||||
|
@@ -6,13 +6,13 @@ import {
|
|||||||
useUserSettingsState
|
useUserSettingsState
|
||||||
} from '../states/SettingsState';
|
} from '../states/SettingsState';
|
||||||
|
|
||||||
interface formatDecmimalOptionsType {
|
interface FormatDecmimalOptionsInterface {
|
||||||
digits?: number;
|
digits?: number;
|
||||||
minDigits?: number;
|
minDigits?: number;
|
||||||
locale?: string;
|
locale?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface formatCurrencyOptionsType {
|
interface FormatCurrencyOptionsInterface {
|
||||||
digits?: number;
|
digits?: number;
|
||||||
minDigits?: number;
|
minDigits?: number;
|
||||||
currency?: string;
|
currency?: string;
|
||||||
@@ -22,7 +22,7 @@ interface formatCurrencyOptionsType {
|
|||||||
|
|
||||||
export function formatDecimal(
|
export function formatDecimal(
|
||||||
value: number | null | undefined,
|
value: number | null | undefined,
|
||||||
options: formatDecmimalOptionsType = {}
|
options: FormatDecmimalOptionsInterface = {}
|
||||||
) {
|
) {
|
||||||
let locale = options.locale || navigator.language || 'en-US';
|
let locale = options.locale || navigator.language || 'en-US';
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ export function formatDecimal(
|
|||||||
*/
|
*/
|
||||||
export function formatCurrency(
|
export function formatCurrency(
|
||||||
value: number | string | null | undefined,
|
value: number | string | null | undefined,
|
||||||
options: formatCurrencyOptionsType = {}
|
options: FormatCurrencyOptionsInterface = {}
|
||||||
) {
|
) {
|
||||||
if (value == null || value == undefined) {
|
if (value == null || value == undefined) {
|
||||||
return null;
|
return null;
|
||||||
@@ -90,7 +90,7 @@ export function formatCurrency(
|
|||||||
export function formatPriceRange(
|
export function formatPriceRange(
|
||||||
minValue: number | null,
|
minValue: number | null,
|
||||||
maxValue: number | null,
|
maxValue: number | null,
|
||||||
options: formatCurrencyOptionsType = {}
|
options: FormatCurrencyOptionsInterface = {}
|
||||||
) {
|
) {
|
||||||
// If neither values are provided, return a dash
|
// If neither values are provided, return a dash
|
||||||
if (minValue == null && maxValue == null) {
|
if (minValue == null && maxValue == null) {
|
||||||
@@ -117,7 +117,7 @@ export function formatPriceRange(
|
|||||||
)}`;
|
)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface renderDateOptionsType {
|
interface RenderDateOptionsInterface {
|
||||||
showTime?: boolean;
|
showTime?: boolean;
|
||||||
showSeconds?: boolean;
|
showSeconds?: boolean;
|
||||||
}
|
}
|
||||||
@@ -128,7 +128,10 @@ interface renderDateOptionsType {
|
|||||||
* The provided "date" variable is a string, nominally ISO format e.g. 2022-02-22
|
* The provided "date" variable is a string, nominally ISO format e.g. 2022-02-22
|
||||||
* The user-configured setting DATE_DISPLAY_FORMAT determines how the date should be displayed.
|
* The user-configured setting DATE_DISPLAY_FORMAT determines how the date should be displayed.
|
||||||
*/
|
*/
|
||||||
export function renderDate(date: string, options: renderDateOptionsType = {}) {
|
export function renderDate(
|
||||||
|
date: string,
|
||||||
|
options: RenderDateOptionsInterface = {}
|
||||||
|
) {
|
||||||
if (!date) {
|
if (!date) {
|
||||||
return '-';
|
return '-';
|
||||||
}
|
}
|
||||||
|
@@ -217,7 +217,7 @@ type InvenTreeIconProps = {
|
|||||||
iconProps?: TablerIconProps;
|
iconProps?: TablerIconProps;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function InvenTreeIcon(props: InvenTreeIconProps) {
|
export function InvenTreeIcon(props: Readonly<InvenTreeIconProps>) {
|
||||||
let Icon: React.ForwardRefExoticComponent<React.RefAttributes<any>>;
|
let Icon: React.ForwardRefExoticComponent<React.RefAttributes<any>>;
|
||||||
|
|
||||||
if (props.icon in icons) {
|
if (props.icon in icons) {
|
||||||
|
91
src/frontend/src/hooks/UseFilter.tsx
Normal file
91
src/frontend/src/hooks/UseFilter.tsx
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Custom hook for retrieving a list of items from the API,
|
||||||
|
* and turning them into "filters" for use in the frontend table framework.
|
||||||
|
*/
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
|
import { api } from '../App';
|
||||||
|
import { ApiEndpoints } from '../enums/ApiEndpoints';
|
||||||
|
import { resolveItem } from '../functions/conversion';
|
||||||
|
import { apiUrl } from '../states/ApiState';
|
||||||
|
import { TableFilterChoice } from '../tables/Filter';
|
||||||
|
|
||||||
|
type UseFilterProps = {
|
||||||
|
url: string;
|
||||||
|
method?: 'GET' | 'POST' | 'OPTIONS';
|
||||||
|
params?: any;
|
||||||
|
accessor?: string;
|
||||||
|
transform: (item: any) => TableFilterChoice;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useFilters(props: UseFilterProps) {
|
||||||
|
const query = useQuery({
|
||||||
|
enabled: true,
|
||||||
|
queryKey: [props.url, props.method, props.params],
|
||||||
|
queryFn: async () => {
|
||||||
|
return await api
|
||||||
|
.request({
|
||||||
|
url: props.url,
|
||||||
|
method: props.method || 'GET',
|
||||||
|
params: props.params
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
let data = resolveItem(response, props.accessor ?? 'data');
|
||||||
|
|
||||||
|
if (data == null || data == undefined) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
})
|
||||||
|
.catch((error) => []);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const choices: TableFilterChoice[] = useMemo(() => {
|
||||||
|
return query.data?.map(props.transform) ?? [];
|
||||||
|
}, [props.transform, query.data]);
|
||||||
|
|
||||||
|
const refresh = useCallback(() => {
|
||||||
|
query.refetch();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
choices,
|
||||||
|
refresh
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide list of project code filters
|
||||||
|
export function useProjectCodeFilters() {
|
||||||
|
return useFilters({
|
||||||
|
url: apiUrl(ApiEndpoints.project_code_list),
|
||||||
|
transform: (item) => ({
|
||||||
|
value: item.pk,
|
||||||
|
label: item.code
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide list of user filters
|
||||||
|
export function useUserFilters() {
|
||||||
|
return useFilters({
|
||||||
|
url: apiUrl(ApiEndpoints.user_list),
|
||||||
|
transform: (item) => ({
|
||||||
|
value: item.pk,
|
||||||
|
label: item.username
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide list of owner filters
|
||||||
|
export function useOwnerFilters() {
|
||||||
|
return useFilters({
|
||||||
|
url: apiUrl(ApiEndpoints.owner_list),
|
||||||
|
transform: (item) => ({
|
||||||
|
value: item.pk,
|
||||||
|
label: item.name
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
@@ -4,6 +4,7 @@ import {
|
|||||||
Badge,
|
Badge,
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
Col,
|
||||||
Container,
|
Container,
|
||||||
Grid,
|
Grid,
|
||||||
Group,
|
Group,
|
||||||
@@ -479,11 +480,11 @@ enum InputMethod {
|
|||||||
ImageBarcode = 'imageBarcode'
|
ImageBarcode = 'imageBarcode'
|
||||||
}
|
}
|
||||||
|
|
||||||
interface inputProps {
|
interface ScanInputInterface {
|
||||||
action: (items: ScanItem[]) => void;
|
action: (items: ScanItem[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function InputManual({ action }: inputProps) {
|
function InputManual({ action }: Readonly<ScanInputInterface>) {
|
||||||
const [value, setValue] = useState<string>('');
|
const [value, setValue] = useState<string>('');
|
||||||
|
|
||||||
function btnAddItem() {
|
function btnAddItem() {
|
||||||
@@ -535,7 +536,7 @@ function InputManual({ action }: inputProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Input that uses QR code detection from images */
|
/* Input that uses QR code detection from images */
|
||||||
function InputImageBarcode({ action }: inputProps) {
|
function InputImageBarcode({ action }: Readonly<ScanInputInterface>) {
|
||||||
const [qrCodeScanner, setQrCodeScanner] = useState<Html5Qrcode | null>(null);
|
const [qrCodeScanner, setQrCodeScanner] = useState<Html5Qrcode | null>(null);
|
||||||
const [camId, setCamId] = useLocalStorage<CameraDevice | null>({
|
const [camId, setCamId] = useLocalStorage<CameraDevice | null>({
|
||||||
key: 'camId',
|
key: 'camId',
|
||||||
|
@@ -56,7 +56,7 @@ export type CompanyDetailProps = {
|
|||||||
/**
|
/**
|
||||||
* Detail view for a single company instance
|
* Detail view for a single company instance
|
||||||
*/
|
*/
|
||||||
export default function CompanyDetail(props: CompanyDetailProps) {
|
export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
|
@@ -104,7 +104,7 @@ export function RowActions({
|
|||||||
}, [actions]);
|
}, [actions]);
|
||||||
|
|
||||||
// Render a single action icon
|
// Render a single action icon
|
||||||
function RowActionIcon(action: RowAction) {
|
function RowActionIcon(action: Readonly<RowAction>) {
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
withinPortal={true}
|
withinPortal={true}
|
||||||
|
@@ -10,6 +10,11 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
|||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { UserRoles } from '../../enums/Roles';
|
import { UserRoles } from '../../enums/Roles';
|
||||||
import { useBuildOrderFields } from '../../forms/BuildForms';
|
import { useBuildOrderFields } from '../../forms/BuildForms';
|
||||||
|
import {
|
||||||
|
useOwnerFilters,
|
||||||
|
useProjectCodeFilters,
|
||||||
|
useUserFilters
|
||||||
|
} from '../../hooks/UseFilter';
|
||||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||||
import { useTable } from '../../hooks/UseTable';
|
import { useTable } from '../../hooks/UseTable';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
@@ -92,6 +97,10 @@ export function BuildOrderTable({
|
|||||||
}) {
|
}) {
|
||||||
const tableColumns = useMemo(() => buildOrderTableColumns(), []);
|
const tableColumns = useMemo(() => buildOrderTableColumns(), []);
|
||||||
|
|
||||||
|
const projectCodeFilters = useProjectCodeFilters();
|
||||||
|
const userFilters = useUserFilters();
|
||||||
|
const responsibleFilters = useOwnerFilters();
|
||||||
|
|
||||||
const tableFilters: TableFilter[] = useMemo(() => {
|
const tableFilters: TableFilter[] = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -117,18 +126,36 @@ export function BuildOrderTable({
|
|||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
label: t`Assigned to me`,
|
label: t`Assigned to me`,
|
||||||
description: t`Show orders assigned to me`
|
description: t`Show orders assigned to me`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'project_code',
|
||||||
|
label: t`Project Code`,
|
||||||
|
description: t`Filter by project code`,
|
||||||
|
choices: projectCodeFilters.choices
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'has_project_code',
|
||||||
|
label: t`Has Project Code`,
|
||||||
|
description: t`Filter by whether the purchase order has a project code`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'issued_by',
|
||||||
|
label: t`Issued By`,
|
||||||
|
description: t`Filter by user who issued this order`,
|
||||||
|
choices: userFilters.choices
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'assigned_to',
|
||||||
|
label: t`Responsible`,
|
||||||
|
description: t`Filter by responsible owner`,
|
||||||
|
choices: responsibleFilters.choices
|
||||||
}
|
}
|
||||||
// TODO: 'assigned to' filter
|
|
||||||
// TODO: 'issued by' filter
|
|
||||||
// {
|
|
||||||
// name: 'has_project_code',
|
|
||||||
// title: t`Has Project Code`,
|
|
||||||
// description: t`Show orders with project code`,
|
|
||||||
// }
|
|
||||||
// TODO: 'has project code' filter (see table_filters.js)
|
|
||||||
// TODO: 'project code' filter (see table_filters.js)
|
|
||||||
];
|
];
|
||||||
}, []);
|
}, [
|
||||||
|
projectCodeFilters.choices,
|
||||||
|
userFilters.choices,
|
||||||
|
responsibleFilters.choices
|
||||||
|
]);
|
||||||
|
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
|
|
||||||
|
@@ -116,13 +116,13 @@ export default function BuildOutputTable({
|
|||||||
/>,
|
/>,
|
||||||
<ActionButton
|
<ActionButton
|
||||||
tooltip={t`Scrap selected outputs`}
|
tooltip={t`Scrap selected outputs`}
|
||||||
icon={<InvenTreeIcon icon="cancel" />}
|
icon={<InvenTreeIcon icon="delete" />}
|
||||||
color="red"
|
color="red"
|
||||||
disabled={!table.hasSelectedRecords}
|
disabled={!table.hasSelectedRecords}
|
||||||
/>,
|
/>,
|
||||||
<ActionButton
|
<ActionButton
|
||||||
tooltip={t`Cancel selected outputs`}
|
tooltip={t`Cancel selected outputs`}
|
||||||
icon={<InvenTreeIcon icon="delete" />}
|
icon={<InvenTreeIcon icon="cancel" />}
|
||||||
color="red"
|
color="red"
|
||||||
disabled={!table.hasSelectedRecords}
|
disabled={!table.hasSelectedRecords}
|
||||||
/>
|
/>
|
||||||
@@ -153,14 +153,14 @@ export default function BuildOutputTable({
|
|||||||
{
|
{
|
||||||
title: t`Scrap`,
|
title: t`Scrap`,
|
||||||
tooltip: t`Scrap build output`,
|
tooltip: t`Scrap build output`,
|
||||||
color: 'red',
|
icon: <InvenTreeIcon icon="delete" />,
|
||||||
icon: <InvenTreeIcon icon="cancel" />
|
color: 'red'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t`Delete`,
|
title: t`Cancel`,
|
||||||
tooltip: t`Delete build output`,
|
tooltip: t`Cancel build output`,
|
||||||
color: 'red',
|
icon: <InvenTreeIcon icon="cancel" />,
|
||||||
icon: <InvenTreeIcon icon="delete" />
|
color: 'red'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Group, Text } from '@mantine/core';
|
import { Group, Text } from '@mantine/core';
|
||||||
import { ReactNode, useMemo } from 'react';
|
import { ReactNode, useMemo } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||||
import { formatPriceRange } from '../../defaults/formatters';
|
import { formatPriceRange } from '../../defaults/formatters';
|
||||||
@@ -9,7 +8,6 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
|||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { UserRoles } from '../../enums/Roles';
|
import { UserRoles } from '../../enums/Roles';
|
||||||
import { usePartFields } from '../../forms/PartForms';
|
import { usePartFields } from '../../forms/PartForms';
|
||||||
import { shortenString } from '../../functions/tables';
|
|
||||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||||
import { useTable } from '../../hooks/UseTable';
|
import { useTable } from '../../hooks/UseTable';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
@@ -43,13 +41,7 @@ function partTableColumns(): TableColumn[] {
|
|||||||
{
|
{
|
||||||
accessor: 'category',
|
accessor: 'category',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
|
render: (record: any) => record.category_detail?.pathstring
|
||||||
render: function (record: any) {
|
|
||||||
// TODO: Link to the category detail page
|
|
||||||
return shortenString({
|
|
||||||
str: record.category_detail?.pathstring
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'total_in_stock',
|
accessor: 'total_in_stock',
|
||||||
|
@@ -52,7 +52,11 @@ type ThumbProps = {
|
|||||||
/**
|
/**
|
||||||
* Renders a single image thumbnail
|
* Renders a single image thumbnail
|
||||||
*/
|
*/
|
||||||
function PartThumbComponent({ selected, element, selectImage }: ThumbProps) {
|
function PartThumbComponent({
|
||||||
|
selected,
|
||||||
|
element,
|
||||||
|
selectImage
|
||||||
|
}: Readonly<ThumbProps>) {
|
||||||
const { hovered, ref } = useHover();
|
const { hovered, ref } = useHover();
|
||||||
|
|
||||||
const hoverColor = 'rgba(127,127,127,0.2)';
|
const hoverColor = 'rgba(127,127,127,0.2)';
|
||||||
|
@@ -8,6 +8,7 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
|||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { UserRoles } from '../../enums/Roles';
|
import { UserRoles } from '../../enums/Roles';
|
||||||
import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms';
|
import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms';
|
||||||
|
import { useOwnerFilters, useProjectCodeFilters } from '../../hooks/UseFilter';
|
||||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||||
import { useTable } from '../../hooks/UseTable';
|
import { useTable } from '../../hooks/UseTable';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
@@ -44,6 +45,9 @@ export function PurchaseOrderTable({
|
|||||||
const table = useTable('purchase-order');
|
const table = useTable('purchase-order');
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
|
|
||||||
|
const projectCodeFilters = useProjectCodeFilters();
|
||||||
|
const responsibleFilters = useOwnerFilters();
|
||||||
|
|
||||||
const tableFilters: TableFilter[] = useMemo(() => {
|
const tableFilters: TableFilter[] = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -54,11 +58,26 @@ export function PurchaseOrderTable({
|
|||||||
},
|
},
|
||||||
OutstandingFilter(),
|
OutstandingFilter(),
|
||||||
OverdueFilter(),
|
OverdueFilter(),
|
||||||
AssignedToMeFilter()
|
AssignedToMeFilter(),
|
||||||
// TODO: has_project_code
|
{
|
||||||
// TODO: project_code
|
name: 'project_code',
|
||||||
|
label: t`Project Code`,
|
||||||
|
description: t`Filter by project code`,
|
||||||
|
choices: projectCodeFilters.choices
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'has_project_code',
|
||||||
|
label: t`Has Project Code`,
|
||||||
|
description: t`Filter by whether the purchase order has a project code`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'assigned_to',
|
||||||
|
label: t`Responsible`,
|
||||||
|
description: t`Filter by responsible owner`,
|
||||||
|
choices: responsibleFilters.choices
|
||||||
|
}
|
||||||
];
|
];
|
||||||
}, []);
|
}, [projectCodeFilters.choices, responsibleFilters.choices]);
|
||||||
|
|
||||||
const tableColumns = useMemo(() => {
|
const tableColumns = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
|
@@ -8,6 +8,7 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
|||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { UserRoles } from '../../enums/Roles';
|
import { UserRoles } from '../../enums/Roles';
|
||||||
import { useReturnOrderFields } from '../../forms/SalesOrderForms';
|
import { useReturnOrderFields } from '../../forms/SalesOrderForms';
|
||||||
|
import { useOwnerFilters, useProjectCodeFilters } from '../../hooks/UseFilter';
|
||||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||||
import { useTable } from '../../hooks/UseTable';
|
import { useTable } from '../../hooks/UseTable';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
@@ -35,6 +36,9 @@ export function ReturnOrderTable({ params }: { params?: any }) {
|
|||||||
const table = useTable('return-orders');
|
const table = useTable('return-orders');
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
|
|
||||||
|
const projectCodeFilters = useProjectCodeFilters();
|
||||||
|
const responsibleFilters = useOwnerFilters();
|
||||||
|
|
||||||
const tableFilters: TableFilter[] = useMemo(() => {
|
const tableFilters: TableFilter[] = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -45,9 +49,26 @@ export function ReturnOrderTable({ params }: { params?: any }) {
|
|||||||
},
|
},
|
||||||
OutstandingFilter(),
|
OutstandingFilter(),
|
||||||
OverdueFilter(),
|
OverdueFilter(),
|
||||||
AssignedToMeFilter()
|
AssignedToMeFilter(),
|
||||||
|
{
|
||||||
|
name: 'project_code',
|
||||||
|
label: t`Project Code`,
|
||||||
|
description: t`Filter by project code`,
|
||||||
|
choices: projectCodeFilters.choices
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'has_project_code',
|
||||||
|
label: t`Has Project Code`,
|
||||||
|
description: t`Filter by whether the purchase order has a project code`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'assigned_to',
|
||||||
|
label: t`Responsible`,
|
||||||
|
description: t`Filter by responsible owner`,
|
||||||
|
choices: responsibleFilters.choices
|
||||||
|
}
|
||||||
];
|
];
|
||||||
}, []);
|
}, [projectCodeFilters.choices, responsibleFilters.choices]);
|
||||||
|
|
||||||
const tableColumns = useMemo(() => {
|
const tableColumns = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
|
@@ -8,6 +8,7 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
|||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { UserRoles } from '../../enums/Roles';
|
import { UserRoles } from '../../enums/Roles';
|
||||||
import { useSalesOrderFields } from '../../forms/SalesOrderForms';
|
import { useSalesOrderFields } from '../../forms/SalesOrderForms';
|
||||||
|
import { useOwnerFilters, useProjectCodeFilters } from '../../hooks/UseFilter';
|
||||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||||
import { useTable } from '../../hooks/UseTable';
|
import { useTable } from '../../hooks/UseTable';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
@@ -41,6 +42,9 @@ export function SalesOrderTable({
|
|||||||
const table = useTable('sales-order');
|
const table = useTable('sales-order');
|
||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
|
|
||||||
|
const projectCodeFilters = useProjectCodeFilters();
|
||||||
|
const responsibleFilters = useOwnerFilters();
|
||||||
|
|
||||||
const tableFilters: TableFilter[] = useMemo(() => {
|
const tableFilters: TableFilter[] = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -51,11 +55,26 @@ export function SalesOrderTable({
|
|||||||
},
|
},
|
||||||
OutstandingFilter(),
|
OutstandingFilter(),
|
||||||
OverdueFilter(),
|
OverdueFilter(),
|
||||||
AssignedToMeFilter()
|
AssignedToMeFilter(),
|
||||||
// TODO: has_project_code
|
{
|
||||||
// TODO: project_code
|
name: 'project_code',
|
||||||
|
label: t`Project Code`,
|
||||||
|
description: t`Filter by project code`,
|
||||||
|
choices: projectCodeFilters.choices
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'has_project_code',
|
||||||
|
label: t`Has Project Code`,
|
||||||
|
description: t`Filter by whether the purchase order has a project code`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'assigned_to',
|
||||||
|
label: t`Responsible`,
|
||||||
|
description: t`Filter by responsible owner`,
|
||||||
|
choices: responsibleFilters.choices
|
||||||
|
}
|
||||||
];
|
];
|
||||||
}, []);
|
}, [projectCodeFilters.choices, responsibleFilters.choices]);
|
||||||
|
|
||||||
const salesOrderFields = useSalesOrderFields();
|
const salesOrderFields = useSalesOrderFields();
|
||||||
|
|
||||||
|
@@ -4,7 +4,7 @@ import { ReactNode, useMemo } from 'react';
|
|||||||
|
|
||||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||||
import { ActionDropdown } from '../../components/items/ActionDropdown';
|
import { ActionDropdown } from '../../components/items/ActionDropdown';
|
||||||
import { formatCurrency, renderDate } from '../../defaults/formatters';
|
import { formatCurrency } from '../../defaults/formatters';
|
||||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||||
import { ModelType } from '../../enums/ModelType';
|
import { ModelType } from '../../enums/ModelType';
|
||||||
import { UserRoles } from '../../enums/Roles';
|
import { UserRoles } from '../../enums/Roles';
|
||||||
|
@@ -27,10 +27,19 @@ test('PUI - Parts', async ({ page }) => {
|
|||||||
await page.getByText('1551ACLR').click();
|
await page.getByText('1551ACLR').click();
|
||||||
await page.getByRole('tab', { name: 'Part Details' }).click();
|
await page.getByRole('tab', { name: 'Part Details' }).click();
|
||||||
await page.getByRole('tab', { name: 'Parameters' }).click();
|
await page.getByRole('tab', { name: 'Parameters' }).click();
|
||||||
// await page.getByRole('tab', { name: 'Stock' }).click();
|
await page
|
||||||
|
.getByRole('tab', { name: 'Part Details' })
|
||||||
|
.locator('xpath=..')
|
||||||
|
.getByRole('tab', { name: 'Stock', exact: true })
|
||||||
|
.click();
|
||||||
await page.getByRole('tab', { name: 'Allocations' }).click();
|
await page.getByRole('tab', { name: 'Allocations' }).click();
|
||||||
await page.getByRole('tab', { name: 'Used In' }).click();
|
await page.getByRole('tab', { name: 'Used In' }).click();
|
||||||
await page.getByRole('tab', { name: 'Pricing' }).click();
|
await page.getByRole('tab', { name: 'Pricing' }).click();
|
||||||
|
|
||||||
|
await page.goto(`${baseUrl}/part/category/index/parts`);
|
||||||
|
await page.getByText('Blue Chair').click();
|
||||||
|
await page.getByRole('tab', { name: 'Bill of Materials' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Build Orders' }).click();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('PUI - Parts - Manufacturer Parts', async ({ page }) => {
|
test('PUI - Parts - Manufacturer Parts', async ({ page }) => {
|
||||||
|
@@ -16,6 +16,14 @@ test('PUI - Stock', async ({ page }) => {
|
|||||||
await page.getByRole('tab', { name: 'Stock Locations' }).click();
|
await page.getByRole('tab', { name: 'Stock Locations' }).click();
|
||||||
await page.getByRole('tab', { name: 'Stock Items' }).click();
|
await page.getByRole('tab', { name: 'Stock Items' }).click();
|
||||||
await page.getByRole('tab', { name: 'Location Details' }).click();
|
await page.getByRole('tab', { name: 'Location Details' }).click();
|
||||||
|
|
||||||
|
await page.goto(`${baseUrl}/stock/item/1194/details`);
|
||||||
|
await page.getByText('D.123 | Doohickey').waitFor();
|
||||||
|
await page.getByText('Batch Code: BX-123-2024-2-7').waitFor();
|
||||||
|
await page.getByRole('tab', { name: 'Stock Tracking' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Test Data' }).click();
|
||||||
|
await page.getByText('395c6d5586e5fb656901d047be27e1f7').waitFor();
|
||||||
|
await page.getByRole('tab', { name: 'Installed Items' }).click();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('PUI - Build', async ({ page }) => {
|
test('PUI - Build', async ({ page }) => {
|
||||||
|
Reference in New Issue
Block a user