1
- import {
2
- AllocationLineItem ,
3
- AllocationLineItemForm ,
4
- } from "./allocate-items-modal"
1
+ import { AllocationLineItemForm } from "./allocate-items-modal"
5
2
import { Controller , useForm , useWatch } from "react-hook-form"
6
3
import { LineItem , ReservationItemDTO } from "@medusajs/medusa"
7
4
import {
8
5
useAdminDeleteReservation ,
9
6
useAdminStockLocations ,
10
7
useAdminUpdateReservation ,
8
+ useAdminVariantsInventory ,
11
9
} from "medusa-react"
12
10
import { useEffect , useMemo } from "react"
13
11
14
12
import Button from "../../../../components/fundamentals/button"
15
13
import CrossIcon from "../../../../components/fundamentals/icons/cross-icon"
16
14
import Select from "../../../../components/molecules/select/next-select/select"
17
15
import SideModal from "../../../../components/molecules/modal/side-modal"
18
- import { nestedForm } from "../../../../utils/nested-form"
19
16
import useNotification from "../../../../hooks/use-notification"
17
+ import Thumbnail from "../../../../components/atoms/thumbnail"
18
+ import { getFulfillableQuantity } from "../create-fulfillment/item-table"
20
19
21
20
type EditAllocationLineItemForm = {
22
21
location : { label : string ; value : string }
@@ -48,6 +47,10 @@ const EditAllocationDrawer = ({
48
47
49
48
const { stock_locations } = useAdminStockLocations ( stockLocationsFilter )
50
49
50
+ const { variant, isLoading } = useAdminVariantsInventory (
51
+ item . variant_id as string
52
+ )
53
+
51
54
const { mutate : updateReservation } = useAdminUpdateReservation (
52
55
reservation ?. id || ""
53
56
)
@@ -126,21 +129,51 @@ const EditAllocationDrawer = ({
126
129
)
127
130
}
128
131
132
+ const { availableQuantity, inStockQuantity } = useMemo ( ( ) => {
133
+ if ( isLoading || ! selectedLocation ?. value || ! variant ) {
134
+ return { }
135
+ }
136
+ const { inventory } = variant
137
+ const locationInventory = inventory [ 0 ] . location_levels ?. find (
138
+ ( inv ) => inv . location_id === selectedLocation ?. value
139
+ )
140
+ if ( ! locationInventory ) {
141
+ return { }
142
+ }
143
+ return {
144
+ availableQuantity : locationInventory . available_quantity ,
145
+ inStockQuantity : locationInventory . stocked_quantity ,
146
+ }
147
+ } , [ variant , selectedLocation , isLoading ] )
148
+
149
+ // we can adjust up to fulfillable quantity - the quantity reserved in other reservations
150
+ const lineItemReservationCapacity =
151
+ getFulfillableQuantity ( item ) -
152
+ ( totalReservedQuantity - ( reservation ?. quantity || 0 ) )
153
+
154
+ const inventoryItemReservationCapacity =
155
+ typeof availableQuantity === "number" ? availableQuantity : 0
156
+
157
+ const maxReservation = Math . min (
158
+ lineItemReservationCapacity ,
159
+ inventoryItemReservationCapacity
160
+ )
161
+
129
162
return (
130
163
< SideModal isVisible close = { close } >
131
164
< form
132
165
className = "text-grey-90 h-full w-full"
133
166
onSubmit = { handleSubmit ( submit ) }
134
167
>
135
- < div className = "flex h-full flex-col justify-between " >
136
- < div >
168
+ < div className = "flex h-full flex-col justify-between" >
169
+ < div className = "flex grow flex-col" >
137
170
< div className = "border-grey-20 flex items-center justify-between border-b px-8 py-6" >
138
171
< h1 className = "inter-large-semibold " > Edit allocation</ h1 >
139
172
< Button variant = "ghost" className = "p-1.5" onClick = { close } >
140
173
< CrossIcon />
141
174
</ Button >
142
175
</ div >
143
- < div className = "flex flex-col gap-y-8 px-8 pt-6" >
176
+ < div className = "flex h-full flex-col justify-between gap-y-8 px-8 pb -8 pt-6" >
144
177
< div >
145
178
< h2 className = "inter-base-semibold" > Location</ h2 >
146
179
< span className = "inter-base-regular text-grey-50" >
@@ -152,28 +185,81 @@ const EditAllocationDrawer = ({
152
185
rules = { { required : true } }
153
186
render = { ( { field : { value, onChange } } ) => (
154
187
< Select
188
+ className = "mt-4"
155
189
value = { value }
156
190
onChange = { onChange }
157
191
options = { locationOptions }
158
192
/>
159
193
) }
160
194
/>
195
+ < div >
196
+ < h2 className = "inter-base-semibold mt-8" >
197
+ Items to Allocate
198
+ </ h2 >
199
+ < span className = "inter-base-regular text-grey-50" >
200
+ Select the number of items that you wish to allocate.
201
+ </ span >
202
+ < div className = "gap-x-base mt-6 flex w-full" >
203
+ < div className = "min-w-9" >
204
+ < Thumbnail size = "medium" src = { item . thumbnail } />
205
+ </ div >
206
+ < div className = "text-grey-50 truncate" >
207
+ < p className = "inter-base-semibold text-grey-90 truncate" >
208
+ { item . title }
209
+ </ p >
210
+ < p className = "inter-base-semibold gap-x-2xsmall flex" >
211
+ < p > { `(${ item . variant . sku } )` } </ p >
212
+ < span > ·</ span >
213
+ < span className = "inter-base-regular gap-x-2xsmall flex" >
214
+ { item . variant . options
215
+ ?. map ( ( option , i ) => [
216
+ < span key = { `${ option . id } -${ i } ` } >
217
+ { option . value }
218
+ </ span > ,
219
+ < span key = { `${ option . id } -${ i } .dot` } > ·</ span > ,
220
+ ] )
221
+ . flat ( )
222
+ . slice ( 0 , - 1 ) ||
223
+ item . variant . title ||
224
+ "-" }
225
+ </ span >
226
+ </ p >
227
+ </ div >
228
+ </ div >
229
+
230
+ < div
231
+ className = { `
232
+ bg-grey-5 text-grey-50 border-grey-20
233
+ mt-8
234
+ grid border-collapse grid-cols-2 grid-rows-3
235
+ [&>*]:border-r [&>*]:border-b [&>*]:py-2
236
+ [&>*:nth-child(odd)]:border-l [&>*:nth-child(odd)]:pl-4
237
+ [&>*:nth-child(even)]:pr-4 [&>*:nth-child(even)]:text-right
238
+ [&>*:nth-child(-n+2)]:border-t` }
239
+ >
240
+ < div className = "rounded-tl-rounded" > In stock</ div >
241
+ < div className = "rounded-tr-rounded" >
242
+ { inStockQuantity ?? "N/A" }
243
+ </ div >
244
+ < div className = "" > Available</ div >
245
+ < div className = "" > { availableQuantity ?? "N/A" } </ div >
246
+ < div className = "rounded-bl-rounded" > Allocate</ div >
247
+ < div className = "bg-grey-0 rounded-br-rounded text-grey-80 flex items-center" >
248
+ < input
249
+ className = "remove-number-spinner inter-base-regular w-full shrink border-none bg-transparent text-right font-normal outline-none outline-0"
250
+ { ...form . register ( "item.quantity" , {
251
+ valueAsNumber : true ,
252
+ } ) }
253
+ type = "number"
254
+ min = { 0 }
255
+ max = { maxReservation }
256
+ />
257
+ < span className = "text-grey-50 nowrap whitespace-nowrap pl-2" > { ` / ${ maxReservation } requested` } </ span >
258
+ </ div >
259
+ </ div >
260
+ </ div >
161
261
</ div >
162
- < div >
163
- < h2 className = "inter-base-semibold" > Items to Allocate</ h2 >
164
- < span className = "inter-base-regular text-grey-50" >
165
- Select the number of items that you wish to allocate.
166
- </ span >
167
- < AllocationLineItem
168
- form = { nestedForm ( form , `item` as "item" ) }
169
- item = { item }
170
- compact
171
- locationId = { selectedLocation ?. value }
172
- reservedQuantity = {
173
- totalReservedQuantity - ( reservation ?. quantity || 0 )
174
- }
175
- />
176
- </ div >
262
+
177
263
< Button
178
264
variant = "ghost"
179
265
className = "my-1 w-full border text-rose-50"
0 commit comments