Skip to content

Commit 5ea92f5

Browse files
authored
Merge branch 'master' into feature/multiplePartImage#t2
2 parents a0ced07 + 5ea3993 commit 5ea92f5

File tree

4 files changed

+178
-24
lines changed

4 files changed

+178
-24
lines changed

src/frontend/src/components/buttons/RemoveRowButton.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
import { t } from '@lingui/core/macro';
22

33
import { ActionButton } from '@lib/components/ActionButton';
4+
import type { FloatingPosition } from '@mantine/core';
45
import { InvenTreeIcon } from '../../functions/icons';
56

67
export default function RemoveRowButton({
78
onClick,
8-
tooltip = t`Remove this row`
9+
tooltip = t`Remove this row`,
10+
tooltipAlignment
911
}: Readonly<{
1012
onClick: () => void;
1113
tooltip?: string;
14+
tooltipAlignment?: FloatingPosition;
1215
}>) {
1316
return (
1417
<ActionButton
1518
onClick={onClick}
1619
icon={<InvenTreeIcon icon='square_x' />}
1720
tooltip={tooltip}
18-
tooltipAlignment='top-end'
21+
tooltipAlignment={tooltipAlignment ?? 'top-end'}
1922
color='red'
2023
/>
2124
);

src/frontend/src/components/render/Company.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export function RenderSupplierPart(
8080
const part = instance.part_detail ?? {};
8181

8282
const secondary: string = instance.SKU;
83-
let suffix: string = part.full_name;
83+
let suffix: string = part?.full_name ?? '';
8484

8585
if (instance.pack_quantity) {
8686
suffix += ` (${instance.pack_quantity})`;

src/frontend/src/components/wizards/OrderPartsWizard.tsx

Lines changed: 172 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,163 @@ import { AddItemButton } from '@lib/components/AddItemButton';
33
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
44
import { ModelType } from '@lib/enums/ModelType';
55
import { apiUrl } from '@lib/functions/Api';
6+
import { formatDecimal } from '@lib/functions/Formatting';
67
import type { ApiFormFieldSet } from '@lib/types/Forms';
78
import { t } from '@lingui/core/macro';
8-
import { Alert, Group, Paper, Tooltip } from '@mantine/core';
9+
import {
10+
ActionIcon,
11+
Alert,
12+
Divider,
13+
Group,
14+
HoverCard,
15+
Loader,
16+
Paper,
17+
Stack,
18+
Text,
19+
Tooltip
20+
} from '@mantine/core';
921
import { showNotification } from '@mantine/notifications';
10-
import { IconShoppingCart } from '@tabler/icons-react';
22+
import {
23+
IconExclamationCircle,
24+
IconInfoCircle,
25+
IconShoppingCart
26+
} from '@tabler/icons-react';
1127
import { DataTable } from 'mantine-datatable';
1228
import { useCallback, useEffect, useMemo, useState } from 'react';
1329
import { useSupplierPartFields } from '../../forms/CompanyForms';
1430
import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms';
1531
import { useCreateApiFormModal } from '../../hooks/UseForm';
32+
import { useInstance } from '../../hooks/UseInstance';
1633
import useWizard from '../../hooks/UseWizard';
1734
import { RenderPartColumn } from '../../tables/ColumnRenderers';
1835
import RemoveRowButton from '../buttons/RemoveRowButton';
1936
import { StandaloneField } from '../forms/StandaloneField';
2037
import Expand from '../items/Expand';
2138

39+
/**
40+
* Render the "requirements" info for a part
41+
* This fetches the information dynamically from the API
42+
*/
43+
function PartRequirementsInfo({
44+
partId,
45+
onQuantityChange
46+
}: {
47+
partId: number | string;
48+
onQuantityChange?: (quantity: number) => void;
49+
}) {
50+
const [requiredQuantity, setRequiredQuantity] = useState<number>(0);
51+
52+
// Notify parent component of quantity change
53+
useEffect(() => {
54+
onQuantityChange?.(requiredQuantity);
55+
}, [requiredQuantity]);
56+
57+
const requirements = useInstance({
58+
endpoint: ApiEndpoints.part_requirements,
59+
pk: partId,
60+
hasPrimaryKey: true,
61+
defaultValue: {}
62+
});
63+
64+
const widget = useMemo(() => {
65+
if (
66+
requirements.instanceQuery.isFetching ||
67+
requirements.instanceQuery.isLoading
68+
) {
69+
return <Loader size='sm' />;
70+
}
71+
72+
if (requirements.instanceQuery.isError) {
73+
return (
74+
<Tooltip label={t`Error fetching part requirements`}>
75+
<ActionIcon variant='transparent' color='red'>
76+
<IconExclamationCircle />
77+
</ActionIcon>
78+
</Tooltip>
79+
);
80+
}
81+
82+
// Calculate the total requirements
83+
const buildRequirements =
84+
requirements.instance?.required_for_build_orders || 0;
85+
const salesRequirements =
86+
requirements.instance?.required_for_sales_orders || 0;
87+
const totalRequirements = buildRequirements + salesRequirements;
88+
89+
const building = requirements.instance?.building || 0;
90+
const ordering = requirements.instance?.ordering || 0;
91+
const incoming = building + ordering;
92+
93+
const inStock = requirements.instance?.total_stock || 0;
94+
95+
const required = Math.max(0, totalRequirements - inStock - incoming);
96+
97+
setRequiredQuantity(required);
98+
99+
return (
100+
<HoverCard position='bottom-end'>
101+
<HoverCard.Target>
102+
<ActionIcon
103+
variant='transparent'
104+
color={required > 0 ? 'blue' : 'green'}
105+
size='sm'
106+
>
107+
<IconInfoCircle />
108+
</ActionIcon>
109+
</HoverCard.Target>
110+
<HoverCard.Dropdown>
111+
<Stack gap='xs'>
112+
<Text>{t`Requirements`}</Text>
113+
<Divider />
114+
{buildRequirements > 0 && (
115+
<Group justify='space-between'>
116+
<Text size='xs'>{t`Build Requirements`}</Text>
117+
<Text size='xs'>{formatDecimal(buildRequirements)}</Text>
118+
</Group>
119+
)}
120+
{salesRequirements > 0 && (
121+
<Group justify='space-between'>
122+
<Text size='xs'>{t`Sales Requirements`}</Text>
123+
<Text size='xs'>{formatDecimal(salesRequirements)}</Text>
124+
</Group>
125+
)}
126+
{inStock > 0 && (
127+
<Group justify='space-between'>
128+
<Text size='xs'>{t`In Stock`}</Text>
129+
<Text size='xs'>{formatDecimal(inStock)}</Text>
130+
</Group>
131+
)}
132+
{ordering > 0 && (
133+
<Group justify='space-between'>
134+
<Text size='xs'>{t`On Order`}</Text>
135+
<Text size='xs'>{formatDecimal(ordering)}</Text>
136+
</Group>
137+
)}
138+
{building > 0 && (
139+
<Group justify='space-between'>
140+
<Text size='xs'>{t`In Production`}</Text>
141+
<Text size='xs'>{formatDecimal(building)}</Text>
142+
</Group>
143+
)}
144+
<Group justify='space-between'>
145+
<Text size='xs'>{t`Required Quantity`}</Text>
146+
<Text size='xs'>{formatDecimal(required)}</Text>
147+
</Group>
148+
</Stack>
149+
</HoverCard.Dropdown>
150+
</HoverCard>
151+
);
152+
}, [
153+
requirements.instanceQuery.isFetching,
154+
requirements.instanceQuery.isLoading,
155+
requirements.instanceQuery.isError,
156+
requirements.instance,
157+
setRequiredQuantity
158+
]);
159+
160+
return widget;
161+
}
162+
22163
/**
23164
* Attributes for each selected part
24165
* - part: The part instance
@@ -123,7 +264,10 @@ function SelectPartsStep({
123264
width: '1%',
124265
render: (record: PartOrderRecord) => (
125266
<Group gap='xs' wrap='nowrap' justify='left'>
126-
<RemoveRowButton onClick={() => onRemovePart(record.part)} />
267+
<RemoveRowButton
268+
tooltipAlignment={'top-start'}
269+
onClick={() => onRemovePart(record.part)}
270+
/>
127271
</Group>
128272
)
129273
},
@@ -162,14 +306,15 @@ function SelectPartsStep({
162306
filters: {
163307
part: record.part.pk,
164308
active: true,
309+
part_detail: true,
165310
supplier_detail: true
166311
}
167312
}}
168313
/>
169314
</Expand>
170315
<AddItemButton
171316
tooltip={t`New supplier part`}
172-
tooltipAlignment='top'
317+
tooltipAlignment='top-end'
173318
onClick={() => {
174319
setSelectedRecord(record);
175320
newSupplierPart.open();
@@ -207,7 +352,7 @@ function SelectPartsStep({
207352
</Expand>
208353
<AddItemButton
209354
tooltip={t`New purchase order`}
210-
tooltipAlignment='top'
355+
tooltipAlignment='top-end'
211356
disabled={!record.supplier_part?.pk}
212357
onClick={() => {
213358
setSelectedRecord(record);
@@ -220,21 +365,29 @@ function SelectPartsStep({
220365
{
221366
accessor: 'quantity',
222367
title: t`Quantity`,
223-
width: 125,
368+
width: 150,
224369
render: (record: PartOrderRecord) => (
225-
<StandaloneField
226-
fieldName='quantity'
227-
hideLabels={true}
228-
error={record.errors?.quantity}
229-
fieldDefinition={{
230-
field_type: 'number',
231-
required: true,
232-
value: record.quantity,
233-
onValueChange: (value) => {
234-
onSelectQuantity(record.part.pk, value);
370+
<Group gap='xs' wrap='nowrap'>
371+
<StandaloneField
372+
fieldName='quantity'
373+
hideLabels={true}
374+
error={record.errors?.quantity}
375+
fieldDefinition={{
376+
field_type: 'number',
377+
required: true,
378+
value: record.quantity,
379+
onValueChange: (value) => {
380+
onSelectQuantity(record.part.pk, value);
381+
}
382+
}}
383+
/>
384+
<PartRequirementsInfo
385+
partId={record.part.pk}
386+
onQuantityChange={(quantity: number) =>
387+
onSelectQuantity(record.part.pk, quantity)
235388
}
236-
}}
237-
/>
389+
/>
390+
</Group>
238391
)
239392
},
240393
{
@@ -255,7 +408,7 @@ function SelectPartsStep({
255408
}
256409
icon={<IconShoppingCart />}
257410
tooltip={t`Add to selected purchase order`}
258-
tooltipAlignment='top'
411+
tooltipAlignment='top-end'
259412
color='blue'
260413
/>
261414
</Group>

src/frontend/src/tables/build/BuildLineTable.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -870,8 +870,6 @@ export default function BuildLineTable({
870870
*/
871871
const formatRecords = useCallback(
872872
(records: any[]): any[] => {
873-
console.log('format records:', records);
874-
875873
return records.map((record) => {
876874
let allocations = [...record.allocations];
877875

0 commit comments

Comments
 (0)