@@ -3,22 +3,163 @@ import { AddItemButton } from '@lib/components/AddItemButton';
33import { ApiEndpoints } from '@lib/enums/ApiEndpoints' ;
44import { ModelType } from '@lib/enums/ModelType' ;
55import { apiUrl } from '@lib/functions/Api' ;
6+ import { formatDecimal } from '@lib/functions/Formatting' ;
67import type { ApiFormFieldSet } from '@lib/types/Forms' ;
78import { 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' ;
921import { showNotification } from '@mantine/notifications' ;
10- import { IconShoppingCart } from '@tabler/icons-react' ;
22+ import {
23+ IconExclamationCircle ,
24+ IconInfoCircle ,
25+ IconShoppingCart
26+ } from '@tabler/icons-react' ;
1127import { DataTable } from 'mantine-datatable' ;
1228import { useCallback , useEffect , useMemo , useState } from 'react' ;
1329import { useSupplierPartFields } from '../../forms/CompanyForms' ;
1430import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms' ;
1531import { useCreateApiFormModal } from '../../hooks/UseForm' ;
32+ import { useInstance } from '../../hooks/UseInstance' ;
1633import useWizard from '../../hooks/UseWizard' ;
1734import { RenderPartColumn } from '../../tables/ColumnRenderers' ;
1835import RemoveRowButton from '../buttons/RemoveRowButton' ;
1936import { StandaloneField } from '../forms/StandaloneField' ;
2037import 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 >
0 commit comments