@apptimate/ui 6.4.0 → 6.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apptimate/ui",
3
- "version": "6.4.0",
3
+ "version": "6.6.0",
4
4
  "main": "src/index.tsx",
5
5
  "types": "src/index.tsx",
6
6
  "dependencies": {
@@ -12,7 +12,11 @@ export interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElemen
12
12
  export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
13
13
  ({ label, className, indeterminate, ...props }, ref) => {
14
14
  return (
15
- <label className={cn("flex items-center gap-2.5 cursor-pointer select-none group", className)}>
15
+ <label className={cn(
16
+ "flex items-center gap-2.5 select-none group",
17
+ props.disabled ? "opacity-60 cursor-not-allowed" : "cursor-pointer",
18
+ className
19
+ )}>
16
20
  <div className="relative flex items-center justify-center">
17
21
  <input
18
22
  ref={ref}
@@ -21,7 +25,7 @@ export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
21
25
  {...props}
22
26
  />
23
27
  <div className={cn(
24
- "w-5 h-5 bg-surface-0 border-2 border-border-subtle rounded-md transition-all peer-checked:bg-primary peer-checked:border-primary peer-focus-visible:ring-2 peer-focus-visible:ring-primary/30 group-hover:border-primary/50 peer-disabled:opacity-50 peer-disabled:bg-surface-1 peer-disabled:border-border-default peer-disabled:cursor-not-allowed",
28
+ "w-5 h-5 bg-surface-0 border-2 border-border-subtle rounded-md transition-all peer-checked:bg-primary peer-checked:border-primary peer-focus-visible:ring-2 peer-focus-visible:ring-primary/30 group-hover:border-primary/50",
25
29
  indeterminate && "bg-primary border-primary"
26
30
  )} />
27
31
  {indeterminate ? (
@@ -37,7 +41,10 @@ export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
37
41
  )}
38
42
  </div>
39
43
  {label && (
40
- <span className="text-[13px] font-medium text-foreground-1 leading-tight peer-disabled:text-foreground-disabled">
44
+ <span className={cn(
45
+ "text-[13px] font-medium leading-tight",
46
+ props.disabled ? "text-foreground-muted" : "text-foreground-1"
47
+ )}>
41
48
  {label}
42
49
  </span>
43
50
  )}
@@ -61,7 +61,7 @@ export const TCell = ({ children, className, isHeader = false, colSpan, rowSpan,
61
61
  className
62
62
  )}>
63
63
  {label && <span className="lg:hidden text-[11px] font-bold text-gray-400 uppercase tracking-wider shrink-0 mr-4">{label}</span>}
64
- <div className="flex justify-end lg:block flex-1 min-w-0 lg:w-auto">{children}</div>
64
+ <div className={cn("flex lg:block flex-1 min-w-0 lg:w-auto", colSpan ? "justify-center" : "justify-end")}>{children}</div>
65
65
  </Tag>
66
66
  );
67
67
  };
@@ -102,13 +102,13 @@ export default function ItemFormWizard({
102
102
  sequence_length: 4,
103
103
  current_sequence: 1,
104
104
  });
105
-
105
+
106
106
  const fetchSkuConfig = useCallback(() => {
107
107
  getInventorySetting("sku_generation_rule_v3").then((res) => {
108
108
  if (res.is_success && res.result) {
109
109
  setSkuConfig(res.result);
110
110
  }
111
- }).catch(() => {});
111
+ }).catch(() => { });
112
112
  }, []);
113
113
 
114
114
  // Fetch initial config
@@ -133,8 +133,19 @@ export default function ItemFormWizard({
133
133
  }
134
134
  }, [formData.tracking_type, formData.valuation_method, update]);
135
135
 
136
+ React.useEffect(() => {
137
+ if (!formData.is_available_purchase && formData.valuation_method === 'mac') {
138
+ update('valuation_method', 'fifo');
139
+ }
140
+ }, [formData.is_available_purchase, formData.valuation_method, update]);
141
+
136
142
  const canNext = (): boolean => {
137
143
  if (step === 0) return Boolean(formData.name && formData.sku && formData.category_id && formData.uom_id);
144
+ if (step === 1 && formData.has_variants) {
145
+ if (!formData.attributes || formData.attributes.length === 0) return false;
146
+ if (formData.attributes.some(attr => !attr.name || attr.name.trim() === "")) return false;
147
+ if (formData.attributes.some(attr => !attr.values || attr.values.length === 0)) return false;
148
+ }
138
149
  return true;
139
150
  };
140
151
 
@@ -163,7 +174,7 @@ export default function ItemFormWizard({
163
174
  <label className="text-[11px] font-normal text-gray-500 tracking-normal apptimate-input-label">
164
175
  SKU <span className="text-danger-alt ml-0.5">*</span>
165
176
  </label>
166
- <button
177
+ <button
167
178
  type="button"
168
179
  onClick={() => setSkuModalOpen(true)}
169
180
  className="text-[11px] font-semibold text-gray-900 hover:text-gray-700 transition-colors"
@@ -171,10 +182,10 @@ export default function ItemFormWizard({
171
182
  Generate
172
183
  </button>
173
184
  </div>
174
- <Input
175
- placeholder="e.g. ITM-001"
176
- value={formData.sku}
177
- onChange={(e) => update("sku", e.target.value)}
185
+ <Input
186
+ placeholder="e.g. ITM-001"
187
+ value={formData.sku}
188
+ onChange={(e) => update("sku", e.target.value)}
178
189
  />
179
190
  </div>
180
191
  </div>
@@ -199,7 +210,7 @@ export default function ItemFormWizard({
199
210
  <div className="space-y-3">
200
211
  <UomPicker label="Stocking UOM (Base)" isRequired value={formData.uom_id || null} displayValue={formData.uom_name || null} baseUnitsOnly
201
212
  onChange={(uom: any) => setFormData((p) => ({ ...p, uom_id: uom ? String(uom.id) : "", uom_name: uom?.name || "" }))} />
202
-
213
+
203
214
  <div className="grid grid-cols-3 gap-3">
204
215
  <Input label="Weight" type="number" placeholder="0" value={formData.weight} onChange={(e) => update("weight", e.target.value)} />
205
216
  <Select label="Weight Unit" options={[{ label: "—", value: "" }, { label: "kg", value: "kg" }, { label: "g", value: "g" }, { label: "lb", value: "lb" }, { label: "oz", value: "oz" }]}
@@ -220,29 +231,27 @@ export default function ItemFormWizard({
220
231
  <div className="p-4 bg-gray-50/50 rounded-xl border border-gray-100">
221
232
  <p className="text-[11px] font-bold text-gray-400 uppercase tracking-wider mb-3">Item Type</p>
222
233
  <div className="flex flex-wrap gap-2">
223
- <button
234
+ <button
224
235
  type="button"
225
- disabled={isEditing && formData.can_edit_variants === false}
226
- title={isEditing && formData.can_edit_variants === false ? "Cannot change item type while transactions exist" : ""}
236
+ disabled={isEditing}
237
+ title={isEditing ? "Cannot change item type of an existing item" : ""}
227
238
  onClick={() => setFormData((p) => ({ ...p, has_variants: false }))}
228
- className={`px-4 py-2 rounded-lg text-sm font-semibold border-2 transition-all duration-150 ${
229
- !formData.has_variants ? "border-primary-500 bg-primary-50 text-primary-700" : "border-gray-200 bg-white text-gray-500 hover:border-gray-300 hover:bg-gray-50"
230
- } ${isEditing && formData.can_edit_variants === false ? "opacity-60 cursor-not-allowed" : ""}`}
239
+ className={`px-4 py-2 rounded-lg text-sm font-semibold border-2 transition-all duration-150 ${!formData.has_variants ? "border-primary-500 bg-primary-50 text-primary-700" : "border-gray-200 bg-white text-gray-500 hover:border-gray-300 hover:bg-gray-50"
240
+ } ${isEditing ? "opacity-60 cursor-not-allowed" : ""}`}
231
241
  >
232
242
  Standard Item (No Variants)
233
243
  </button>
234
- <button
244
+ <button
235
245
  type="button"
236
- disabled={isEditing && formData.can_edit_variants === false}
237
- title={isEditing && formData.can_edit_variants === false ? "Cannot change item type while transactions exist" : ""}
238
- onClick={() => setFormData((p) => ({
239
- ...p,
246
+ disabled={isEditing}
247
+ title={isEditing ? "Cannot change item type of an existing item" : ""}
248
+ onClick={() => setFormData((p) => ({
249
+ ...p,
240
250
  has_variants: true,
241
- attributes: p.attributes.length === 0 ? [{ name: "", values: [] }] : p.attributes
251
+ attributes: p.attributes.length === 0 ? [{ name: "", values: [] }] : p.attributes
242
252
  }))}
243
- className={`px-4 py-2 rounded-lg text-sm font-semibold border-2 transition-all duration-150 flex items-center gap-1.5 ${
244
- formData.has_variants ? "border-primary-500 bg-primary-50 text-primary-700" : "border-gray-200 bg-white text-gray-500 hover:border-gray-300 hover:bg-gray-50"
245
- } ${isEditing && formData.can_edit_variants === false ? "opacity-60 cursor-not-allowed" : ""}`}
253
+ className={`px-4 py-2 rounded-lg text-sm font-semibold border-2 transition-all duration-150 flex items-center gap-1.5 ${formData.has_variants ? "border-primary-500 bg-primary-50 text-primary-700" : "border-gray-200 bg-white text-gray-500 hover:border-gray-300 hover:bg-gray-50"
254
+ } ${isEditing ? "opacity-60 cursor-not-allowed" : ""}`}
246
255
  >
247
256
  Item with Variants
248
257
  <HintIcon text="Use this if the item comes in multiple options (e.g., sizes, colors)." />
@@ -255,30 +264,29 @@ export default function ItemFormWizard({
255
264
  <div className="p-4 bg-gray-50/50 rounded-xl border border-gray-100">
256
265
  <div className="flex items-center justify-between mb-3">
257
266
  <p className="text-[11px] font-bold text-gray-400 uppercase tracking-wider">Variant Attributes</p>
258
- <button
267
+ <button
259
268
  type="button"
260
- disabled={isEditing && formData.can_edit_variants === false}
261
- title={isEditing && formData.can_edit_variants === false ? "Cannot add attributes while transactions exist" : ""}
269
+ disabled={isEditing}
270
+ title={isEditing ? "Cannot add attributes to an existing item" : ""}
262
271
  onClick={() => setFormData(p => ({ ...p, attributes: [...p.attributes, { name: "", values: [] }] }))}
263
- className={`flex items-center gap-1.5 text-[12px] font-semibold transition-colors ${
264
- isEditing && formData.can_edit_variants === false ? "text-gray-400 cursor-not-allowed" : "text-primary-600 hover:text-primary-700"
265
- }`}
272
+ className={`flex items-center gap-1.5 text-[12px] font-semibold transition-colors ${isEditing ? "text-gray-400 cursor-not-allowed" : "text-primary-600 hover:text-primary-700"
273
+ }`}
266
274
  >
267
275
  <Plus size={14} /> Add Attribute
268
276
  </button>
269
277
  </div>
270
-
278
+
271
279
  {formData.attributes.length === 0 ? (
272
- <div className="text-center py-4 text-gray-400 text-[13px]">No attributes defined. Click "Add Attribute" to add variants like Color or Size.</div>
280
+ <div className="text-center py-4 text-gray-400 text-[13px]">No attributes defined. Click "Add Attribute" to add variants like Color or Size.</div>
273
281
  ) : (
274
282
  <div className="space-y-4">
275
283
  {formData.attributes.map((attr, idx) => (
276
284
  <div key={idx} className="flex flex-col sm:flex-row items-start gap-4">
277
285
  <div className="w-full sm:w-[250px]">
278
- <Input
279
- placeholder="e.g. Size, Color"
286
+ <Input
287
+ placeholder="e.g. Size, Color"
280
288
  value={attr.name}
281
- disabled={isEditing && formData.can_edit_variants === false}
289
+ disabled={isEditing}
282
290
  onChange={(e) => {
283
291
  const newAttrs = [...formData.attributes];
284
292
  newAttrs[idx] = { ...newAttrs[idx], name: e.target.value };
@@ -287,9 +295,9 @@ export default function ItemFormWizard({
287
295
  />
288
296
  </div>
289
297
  <div className="flex-1 flex items-start gap-2 w-full">
290
- <TagsInput
298
+ <TagsInput
291
299
  values={attr.values}
292
- disabled={isEditing && formData.can_edit_variants === false}
300
+ disabled={isEditing}
293
301
  onChange={(values) => {
294
302
  const newAttrs = [...formData.attributes];
295
303
  newAttrs[idx] = { ...newAttrs[idx], values };
@@ -299,17 +307,16 @@ export default function ItemFormWizard({
299
307
  />
300
308
  <button
301
309
  type="button"
302
- disabled={isEditing && formData.can_edit_variants === false}
303
- title={isEditing && formData.can_edit_variants === false ? "Cannot remove attributes while transactions exist" : ""}
310
+ disabled={isEditing}
311
+ title={isEditing ? "Cannot remove attributes from an existing item" : ""}
304
312
  onClick={() => {
305
313
  const newAttrs = formData.attributes.filter((_, i) => i !== idx);
306
314
  setFormData(p => ({ ...p, attributes: newAttrs }));
307
315
  }}
308
- className={`p-2.5 rounded-lg transition-colors mt-0.5 ${
309
- isEditing && formData.can_edit_variants === false
310
- ? "text-gray-300 cursor-not-allowed"
316
+ className={`p-2.5 rounded-lg transition-colors mt-0.5 ${isEditing
317
+ ? "text-gray-300 cursor-not-allowed"
311
318
  : "text-gray-400 hover:text-danger-alt hover:bg-danger-50"
312
- }`}
319
+ }`}
313
320
  >
314
321
  <Trash size={16} />
315
322
  </button>
@@ -325,41 +332,41 @@ export default function ItemFormWizard({
325
332
  <div className="p-4 bg-gray-50/50 rounded-xl border border-gray-100">
326
333
  <p className="text-[11px] font-bold text-gray-400 uppercase tracking-wider mb-3">Availability & Settings</p>
327
334
  <div className="grid grid-cols-2 sm:grid-cols-3 gap-y-3 gap-x-4">
328
- <Checkbox
329
- label={<span className="flex items-center gap-1.5">Available for Purchase <HintIcon text="Can be purchased from suppliers" /></span>}
330
- checked={formData.is_available_purchase}
331
- onChange={(e: any) => setFormData((p) => ({ ...p, is_available_purchase: e.target.checked }))}
335
+ <Checkbox
336
+ label={<span className="flex items-center gap-1.5">Available for Purchase <HintIcon text="Can be purchased from suppliers" /></span>}
337
+ checked={formData.is_available_purchase}
338
+ onChange={(e: any) => setFormData((p) => ({ ...p, is_available_purchase: e.target.checked }))}
332
339
  />
333
- <Checkbox
334
- label={<span className="flex items-center gap-1.5">Available for Sale <HintIcon text="Can be sold to customers" /></span>}
335
- checked={formData.is_available_sell}
336
- onChange={(e: any) => setFormData((p) => ({ ...p, is_available_sell: e.target.checked }))}
340
+ <Checkbox
341
+ label={<span className="flex items-center gap-1.5">Available for Sale <HintIcon text="Can be sold to customers" /></span>}
342
+ checked={formData.is_available_sell}
343
+ onChange={(e: any) => setFormData((p) => ({ ...p, is_available_sell: e.target.checked }))}
337
344
  />
338
- <Checkbox
339
- label={<span className="flex items-center gap-1.5">Available on E-Commerce <HintIcon text="List on online store" /></span>}
340
- checked={formData.is_available_ecommerce}
341
- onChange={(e: any) => setFormData((p) => ({ ...p, is_available_ecommerce: e.target.checked }))}
345
+ <Checkbox
346
+ label={<span className="flex items-center gap-1.5">Available on E-Commerce <HintIcon text="List on online store" /></span>}
347
+ checked={formData.is_available_ecommerce}
348
+ onChange={(e: any) => setFormData((p) => ({ ...p, is_available_ecommerce: e.target.checked }))}
342
349
  />
343
- <Checkbox
344
- label={<span className="flex items-center gap-1.5">Track Stock <HintIcon text="Monitor inventory levels" /></span>}
345
- checked={formData.has_stocks}
346
- onChange={(e: any) => setFormData((p) => ({ ...p, has_stocks: e.target.checked }))}
350
+ <Checkbox
351
+ label={<span className="flex items-center gap-1.5">Track Stock <HintIcon text="Monitor inventory levels" /></span>}
352
+ checked={formData.has_stocks}
353
+ onChange={(e: any) => setFormData((p) => ({ ...p, has_stocks: e.target.checked }))}
347
354
  disabled={isEditing && formData.can_edit_variants === false}
348
355
  />
349
- <Checkbox
350
- label={<span className="flex items-center gap-1.5">Taxable <HintIcon text="Subject to taxes" /></span>}
351
- checked={formData.is_taxable}
352
- onChange={(e: any) => setFormData((p) => ({ ...p, is_taxable: e.target.checked }))}
356
+ <Checkbox
357
+ label={<span className="flex items-center gap-1.5">Taxable <HintIcon text="Subject to taxes" /></span>}
358
+ checked={formData.is_taxable}
359
+ onChange={(e: any) => setFormData((p) => ({ ...p, is_taxable: e.target.checked }))}
353
360
  />
354
- <Checkbox
355
- label={<span className="flex items-center gap-1.5">Returnable <HintIcon text="Eligible for returns" /></span>}
356
- checked={formData.is_returnable}
357
- onChange={(e: any) => setFormData((p) => ({ ...p, is_returnable: e.target.checked }))}
361
+ <Checkbox
362
+ label={<span className="flex items-center gap-1.5">Returnable <HintIcon text="Eligible for returns" /></span>}
363
+ checked={formData.is_returnable}
364
+ onChange={(e: any) => setFormData((p) => ({ ...p, is_returnable: e.target.checked }))}
358
365
  />
359
- <Checkbox
360
- label={<span className="flex items-center gap-1.5">Discountable <HintIcon text="Eligible for discounts" /></span>}
361
- checked={formData.is_discountable}
362
- onChange={(e: any) => setFormData((p) => ({ ...p, is_discountable: e.target.checked }))}
366
+ <Checkbox
367
+ label={<span className="flex items-center gap-1.5">Discountable <HintIcon text="Eligible for discounts" /></span>}
368
+ checked={formData.is_discountable}
369
+ onChange={(e: any) => setFormData((p) => ({ ...p, is_discountable: e.target.checked }))}
363
370
  />
364
371
  </div>
365
372
  </div>
@@ -369,35 +376,35 @@ export default function ItemFormWizard({
369
376
  <p className="text-[11px] font-bold text-gray-400 uppercase tracking-wider mb-3">Tracking Options</p>
370
377
  <div className="flex flex-col sm:flex-row items-start sm:items-center gap-4 sm:gap-6">
371
378
  <label className="flex items-center gap-2 text-sm text-[#2D3142] cursor-pointer">
372
- <input
373
- type="radio"
374
- name="tracking_type"
379
+ <input
380
+ type="radio"
381
+ name="tracking_type"
375
382
  className="w-4 h-4 text-[#7A52F4] border-gray-300 focus:ring-[#7A52F4] disabled:opacity-50"
376
383
  checked={formData.tracking_type === 'none'}
377
384
  onChange={() => setFormData(p => ({ ...p, tracking_type: 'none' }))}
378
- disabled={isEditing && formData.can_edit_variants === false}
385
+ disabled={isEditing}
379
386
  />
380
387
  <span>None</span>
381
388
  </label>
382
389
  <label className="flex items-center gap-2 text-sm text-[#2D3142] cursor-pointer">
383
- <input
384
- type="radio"
385
- name="tracking_type"
390
+ <input
391
+ type="radio"
392
+ name="tracking_type"
386
393
  className="w-4 h-4 text-[#7A52F4] border-gray-300 focus:ring-[#7A52F4] disabled:opacity-50"
387
394
  checked={formData.tracking_type === 'batch'}
388
395
  onChange={() => setFormData(p => ({ ...p, tracking_type: 'batch' }))}
389
- disabled={isEditing && formData.can_edit_variants === false}
396
+ disabled={isEditing}
390
397
  />
391
398
  <span className="flex items-center gap-1.5">Batches <HintIcon text="Track items by manufacturing batch or lot" /></span>
392
399
  </label>
393
400
  <label className="flex items-center gap-2 text-sm text-[#2D3142] cursor-pointer">
394
- <input
395
- type="radio"
396
- name="tracking_type"
401
+ <input
402
+ type="radio"
403
+ name="tracking_type"
397
404
  className="w-4 h-4 text-[#7A52F4] border-gray-300 focus:ring-[#7A52F4] disabled:opacity-50"
398
405
  checked={formData.tracking_type === 'serial'}
399
406
  onChange={() => setFormData(p => ({ ...p, tracking_type: 'serial' }))}
400
- disabled={isEditing && formData.can_edit_variants === false}
407
+ disabled={isEditing}
401
408
  />
402
409
  <span className="flex items-center gap-1.5">Serial Numbers <HintIcon text="Track each individual unit with a unique serial number" /></span>
403
410
  </label>
@@ -411,9 +418,9 @@ export default function ItemFormWizard({
411
418
  <p className="text-[11px] font-bold text-gray-400 uppercase tracking-wider mb-3">Valuation Method (COGS)</p>
412
419
  <div className="flex flex-col sm:flex-row items-start sm:items-center gap-4 sm:gap-6">
413
420
  <label className="flex items-center gap-2 text-sm text-[#2D3142] cursor-pointer">
414
- <input
415
- type="radio"
416
- name="valuation_method"
421
+ <input
422
+ type="radio"
423
+ name="valuation_method"
417
424
  className="w-4 h-4 text-[#7A52F4] border-gray-300 focus:ring-[#7A52F4] disabled:opacity-50"
418
425
  checked={formData.valuation_method === 'fifo'}
419
426
  onChange={() => setFormData(p => ({ ...p, valuation_method: 'fifo' }))}
@@ -422,9 +429,9 @@ export default function ItemFormWizard({
422
429
  <span>First-In, First-Out (FIFO)</span>
423
430
  </label>
424
431
  <label className="flex items-center gap-2 text-sm text-[#2D3142] cursor-pointer">
425
- <input
426
- type="radio"
427
- name="valuation_method"
432
+ <input
433
+ type="radio"
434
+ name="valuation_method"
428
435
  className="w-4 h-4 text-[#7A52F4] border-gray-300 focus:ring-[#7A52F4] disabled:opacity-50"
429
436
  checked={formData.valuation_method === 'lifo'}
430
437
  onChange={() => setFormData(p => ({ ...p, valuation_method: 'lifo' }))}
@@ -432,17 +439,19 @@ export default function ItemFormWizard({
432
439
  />
433
440
  <span>Last-In, First-Out (LIFO)</span>
434
441
  </label>
435
- <label className="flex items-center gap-2 text-sm text-[#2D3142] cursor-pointer">
436
- <input
437
- type="radio"
438
- name="valuation_method"
439
- className="w-4 h-4 text-[#7A52F4] border-gray-300 focus:ring-[#7A52F4] disabled:opacity-50"
440
- checked={formData.valuation_method === 'mac'}
441
- onChange={() => setFormData(p => ({ ...p, valuation_method: 'mac' }))}
442
- disabled={isEditing && formData.can_edit_variants === false}
443
- />
444
- <span className="flex items-center gap-1.5">Moving Average Cost (MAC)</span>
445
- </label>
442
+ {formData.is_available_purchase && (
443
+ <label className="flex items-center gap-2 text-sm text-[#2D3142] cursor-pointer">
444
+ <input
445
+ type="radio"
446
+ name="valuation_method"
447
+ className="w-4 h-4 text-[#7A52F4] border-gray-300 focus:ring-[#7A52F4] disabled:opacity-50"
448
+ checked={formData.valuation_method === 'mac'}
449
+ onChange={() => setFormData(p => ({ ...p, valuation_method: 'mac' }))}
450
+ disabled={isEditing && formData.can_edit_variants === false}
451
+ />
452
+ <span className="flex items-center gap-1.5">Moving Average Cost (MAC)</span>
453
+ </label>
454
+ )}
446
455
  </div>
447
456
  </div>
448
457
  )}
@@ -549,7 +558,7 @@ export default function ItemFormWizard({
549
558
  <Input label="Sale Price" type="number" placeholder="0.00" value={formData.sale_price} onChange={(e) => update("sale_price", e.target.value)} />
550
559
  )}
551
560
  {formData.is_available_purchase && (
552
- <Input label="Cost Price" type="number" placeholder="0.00" value={formData.cost_price} onChange={(e) => update("cost_price", e.target.value)} />
561
+ <Input label="Cost Price" type="number" placeholder="0.00" value={formData.cost_price} disabled={formData.can_edit_variants === false && formData.valuation_method === 'mac'} onChange={(e) => update("cost_price", e.target.value)} />
553
562
  )}
554
563
  {formData.is_available_sell && (
555
564
  <Input label="Min Sales Price" type="number" placeholder="0.00" value={formData.min_sales_price} onChange={(e) => update("min_sales_price", e.target.value)} />
@@ -637,18 +646,18 @@ export default function ItemFormWizard({
637
646
  )}
638
647
  {formData.is_available_purchase && (
639
648
  <td className="p-2 min-w-[120px] align-top">
640
- <Input type="number" placeholder="0.00" value={v.cost_price} onChange={(e) => updateVariant(idx, "cost_price", e.target.value)} />
649
+ <Input type="number" placeholder="0.00" value={v.cost_price} disabled={formData.can_edit_variants === false && formData.valuation_method === 'mac'} onChange={(e) => updateVariant(idx, "cost_price", e.target.value)} />
641
650
  </td>
642
651
  )}
643
652
  <td className="p-2 text-center align-middle w-12">
644
- <button
645
- type="button"
646
- disabled={isEditing && formData.can_edit_variants === false}
647
- title={isEditing && formData.can_edit_variants === false ? "Cannot delete variants while transactions exist" : ""}
648
- onClick={() => removeVariant(idx)}
653
+ <button
654
+ type="button"
655
+ disabled={isEditing}
656
+ title={isEditing ? "Cannot delete variants of an existing item" : ""}
657
+ onClick={() => removeVariant(idx)}
649
658
  className={cn(
650
659
  "p-1.5 rounded-lg transition-colors mt-1",
651
- isEditing && formData.can_edit_variants === false
660
+ isEditing
652
661
  ? "text-gray-300 cursor-not-allowed"
653
662
  : "text-gray-400 hover:text-danger-500 hover:bg-danger-50"
654
663
  )}
@@ -51,18 +51,45 @@ export function PartyPicker({
51
51
  const [quickAddEmail, setQuickAddEmail] = useState("");
52
52
  const [quickAddPhone, setQuickAddPhone] = useState("");
53
53
  const [quickAddSecondaryContact, setQuickAddSecondaryContact] = useState("");
54
- const [quickAddGender, setQuickAddGender] = useState("");
55
54
  const [quickAddDOB, setQuickAddDOB] = useState("");
55
+ const [quickAddIdentityType, setQuickAddIdentityType] = useState("");
56
+ const [quickAddIdentity, setQuickAddIdentity] = useState("");
57
+
58
+ const [quickAddAddressLine1, setQuickAddAddressLine1] = useState("");
59
+ const [quickAddAddressLine2, setQuickAddAddressLine2] = useState("");
60
+ const [quickAddCity, setQuickAddCity] = useState("");
61
+ const [quickAddState, setQuickAddState] = useState("");
62
+ const [quickAddPostalCode, setQuickAddPostalCode] = useState("");
63
+ const [quickAddCountry, setQuickAddCountry] = useState("");
64
+ const [showQuickAddDetailedAddress, setShowQuickAddDetailedAddress] = useState(false);
65
+
56
66
  const [quickAddCurrencyId, setQuickAddCurrencyId] = useState<string | number | undefined>(undefined);
57
67
  const [quickAddCurrencyDisplay, setQuickAddCurrencyDisplay] = useState<any>(null);
58
68
  const [errors, setErrors] = useState<Record<string, string[]>>({});
59
69
  const [isCreating, setIsCreating] = useState(false);
60
70
 
71
+ const [fieldConfigs, setFieldConfigs] = useState<any[]>([]);
72
+
73
+ const effectiveType = partyType === "all" ? "customer" : partyType;
74
+ const mergedConfig = React.useMemo(() => {
75
+ const configMap: Record<string, { active: boolean; mandatory: boolean }> = {};
76
+ const relevantConfigs = fieldConfigs.filter(c => c.entity === effectiveType);
77
+ for (const config of relevantConfigs) {
78
+ if (!configMap[config.field]) {
79
+ configMap[config.field] = { active: false, mandatory: false };
80
+ }
81
+ if (config.status === "active") configMap[config.field].active = true;
82
+ if (config.is_mandatory) configMap[config.field].mandatory = true;
83
+ }
84
+ return configMap;
85
+ }, [fieldConfigs, effectiveType]);
86
+
61
87
  const hasInitializedQuickAddCurrency = useRef(false);
62
88
 
63
89
  useEffect(() => {
64
- if (isQuickAddOpen && !hasInitializedQuickAddCurrency.current) {
65
- hasInitializedQuickAddCurrency.current = true;
90
+ if (isQuickAddOpen) {
91
+ if (!hasInitializedQuickAddCurrency.current) {
92
+ hasInitializedQuickAddCurrency.current = true;
66
93
  sendRequest({ url: `${process.env.NEXT_PUBLIC_API_URL}/api/settings`, method: 'GET' })
67
94
  .then((res: any) => {
68
95
  if (res.responseData?.is_success && res.responseData?.result) {
@@ -89,8 +116,18 @@ export function PartyPicker({
89
116
  }
90
117
  }
91
118
  });
119
+ }
120
+
121
+ if (fieldConfigs.length === 0) {
122
+ sendRequest({ url: `${process.env.NEXT_PUBLIC_API_URL}/api/field-configs`, method: 'GET' })
123
+ .then((res: any) => {
124
+ if (res.responseData?.is_success && res.responseData?.result) {
125
+ setFieldConfigs(res.responseData.result);
126
+ }
127
+ });
128
+ }
92
129
  }
93
- }, [isQuickAddOpen]);
130
+ }, [isQuickAddOpen, fieldConfigs.length]);
94
131
 
95
132
  const fetchData = useCallback(async (query: string = "") => {
96
133
  setIsLoading(true);
@@ -121,12 +158,17 @@ export function PartyPicker({
121
158
 
122
159
  const handleSelect = (party: any) => {
123
160
  onChange({
124
- id: party.id,
125
- name: party.name,
126
- code: party.code,
127
- type: party.type,
128
- default_currency_id: party.default_currency_id,
129
- default_currency: party.default_currency,
161
+ id: Number(party.id),
162
+ name: String(party.name || ""),
163
+ code: party.code ? String(party.code) : undefined,
164
+ type: Array.isArray(party.type) ? party.type[0] : (party.type ? String(party.type) : undefined),
165
+ default_currency_id: party.default_currency_id ? Number(party.default_currency_id) : undefined,
166
+ default_currency: party.default_currency ? {
167
+ id: Number(party.default_currency.id),
168
+ code: String(party.default_currency.code),
169
+ name: String(party.default_currency.name),
170
+ symbol: String(party.default_currency.symbol),
171
+ } : undefined,
130
172
  });
131
173
  setIsOpen(false);
132
174
  };
@@ -146,11 +188,20 @@ export function PartyPicker({
146
188
  email: quickAddEmail.trim() || undefined,
147
189
  primary_contact: quickAddPhone.trim() || undefined,
148
190
  secondary_contact: quickAddSecondaryContact.trim() || undefined,
149
- gender: quickAddGender || undefined,
150
191
  date_of_birth: quickAddDOB || undefined,
192
+ identity_type: quickAddIdentityType || undefined,
193
+ identity: quickAddIdentity.trim() || undefined,
151
194
  default_currency_id: quickAddCurrencyId || undefined,
152
- type: [partyType === "all" ? "customer" : partyType],
153
- status: "active"
195
+ type: [partyType === "all" || !partyType ? "customer" : partyType],
196
+ status: "active",
197
+ address: quickAddAddressLine1.trim() ? {
198
+ address_line_1: quickAddAddressLine1.trim(),
199
+ address_line_2: quickAddAddressLine2.trim() || undefined,
200
+ city: quickAddCity.trim() || undefined,
201
+ state: quickAddState.trim() || undefined,
202
+ postal_code: quickAddPostalCode.trim() || undefined,
203
+ country: quickAddCountry.trim() || undefined,
204
+ } : undefined
154
205
  };
155
206
  const res = await createParty(payload);
156
207
  if (res.is_success && res.result) {
@@ -158,16 +209,16 @@ export function PartyPicker({
158
209
  const party = res.result;
159
210
  const partyName = party.name || party.full_name || [party.first_name, party.last_name].filter(Boolean).join(" ");
160
211
  onChange({
161
- id: party.id,
162
- name: partyName,
163
- code: party.code,
164
- type: party.type,
165
- default_currency_id: quickAddCurrencyDisplay?.id,
212
+ id: Number(party.id),
213
+ name: String(partyName),
214
+ code: party.code ? String(party.code) : undefined,
215
+ type: Array.isArray(party.type) ? party.type[0] : (party.type ? String(party.type) : undefined),
216
+ default_currency_id: quickAddCurrencyDisplay?.id ? Number(quickAddCurrencyDisplay.id) : undefined,
166
217
  default_currency: quickAddCurrencyDisplay ? {
167
- id: quickAddCurrencyDisplay.id,
168
- code: quickAddCurrencyDisplay.code,
169
- name: quickAddCurrencyDisplay.name,
170
- symbol: quickAddCurrencyDisplay.symbol
218
+ id: Number(quickAddCurrencyDisplay.id),
219
+ code: String(quickAddCurrencyDisplay.code),
220
+ name: String(quickAddCurrencyDisplay.name),
221
+ symbol: String(quickAddCurrencyDisplay.symbol)
171
222
  } : undefined
172
223
  });
173
224
  setIsQuickAddOpen(false);
@@ -176,8 +227,16 @@ export function PartyPicker({
176
227
  setQuickAddEmail("");
177
228
  setQuickAddPhone("");
178
229
  setQuickAddSecondaryContact("");
179
- setQuickAddGender("");
180
230
  setQuickAddDOB("");
231
+ setQuickAddIdentityType("");
232
+ setQuickAddIdentity("");
233
+ setQuickAddAddressLine1("");
234
+ setQuickAddAddressLine2("");
235
+ setQuickAddCity("");
236
+ setQuickAddState("");
237
+ setQuickAddPostalCode("");
238
+ setQuickAddCountry("");
239
+ setShowQuickAddDetailedAddress(false);
181
240
  setErrors({});
182
241
  setIsOpen(false);
183
242
  } else if ((res as any).errors) {
@@ -229,8 +288,16 @@ export function PartyPicker({
229
288
  setQuickAddEmail("");
230
289
  setQuickAddPhone("");
231
290
  setQuickAddSecondaryContact("");
232
- setQuickAddGender("");
233
291
  setQuickAddDOB("");
292
+ setQuickAddIdentityType("");
293
+ setQuickAddIdentity("");
294
+ setQuickAddAddressLine1("");
295
+ setQuickAddAddressLine2("");
296
+ setQuickAddCity("");
297
+ setQuickAddState("");
298
+ setQuickAddPostalCode("");
299
+ setQuickAddCountry("");
300
+ setShowQuickAddDetailedAddress(false);
234
301
  setErrors({});
235
302
  }}
236
303
  className="p-1.5 rounded-lg bg-primary-50 text-primary-600 hover:bg-primary-100 transition-colors flex-shrink-0"
@@ -256,7 +323,11 @@ export function PartyPicker({
256
323
  <PickerItem
257
324
  key={party.id}
258
325
  label={party.name}
259
- sublabel={party.code ? `Code: ${party.code}` : undefined}
326
+ sublabel={[
327
+ party.code ? `Code: ${party.code}` : null,
328
+ party.phone ? `Contact: ${party.phone}` : null,
329
+ party.identity ? `Identity: ${party.identity}` : null
330
+ ].filter(Boolean).join(" | ") || undefined}
260
331
  isSelected={String(value) === String(party.id)}
261
332
  onClick={() => handleSelect(party)}
262
333
  />
@@ -276,71 +347,181 @@ export function PartyPicker({
276
347
  >
277
348
  <div className="space-y-4">
278
349
  <div className="grid grid-cols-2 gap-3">
279
- <Input
280
- label="First Name"
281
- isRequired
282
- placeholder={`Enter ${label.toLowerCase()} first name`}
283
- value={quickAddFirstName}
284
- onChange={(e) => setQuickAddFirstName(e.target.value)}
285
- error={errors.first_name?.[0]}
286
- />
287
- <Input
288
- label="Last Name"
289
- placeholder={`Enter ${label.toLowerCase()} last name`}
290
- value={quickAddLastName}
291
- onChange={(e) => setQuickAddLastName(e.target.value)}
292
- error={errors.last_name?.[0]}
293
- />
350
+ {mergedConfig.first_name?.active !== false && (
351
+ <Input
352
+ label="First Name"
353
+ isRequired={mergedConfig.first_name?.mandatory ?? true}
354
+ placeholder={`Enter ${label.toLowerCase()} first name`}
355
+ value={quickAddFirstName}
356
+ onChange={(e) => setQuickAddFirstName(e.target.value)}
357
+ error={errors.first_name?.[0]}
358
+ />
359
+ )}
360
+ {mergedConfig.last_name?.active !== false && (
361
+ <Input
362
+ label="Last Name"
363
+ isRequired={mergedConfig.last_name?.mandatory}
364
+ placeholder={`Enter ${label.toLowerCase()} last name`}
365
+ value={quickAddLastName}
366
+ onChange={(e) => setQuickAddLastName(e.target.value)}
367
+ error={errors.last_name?.[0]}
368
+ />
369
+ )}
294
370
  </div>
295
371
  <div className="grid grid-cols-2 gap-3">
296
- <Input
297
- label="Email"
298
- type="email"
299
- placeholder="Email address"
300
- value={quickAddEmail}
301
- onChange={(e) => setQuickAddEmail(e.target.value)}
302
- error={errors.email?.[0]}
303
- />
304
- <Input
305
- label="Primary Contact"
306
- placeholder="Primary phone number"
307
- value={quickAddPhone}
308
- onChange={(e) => setQuickAddPhone(e.target.value)}
309
- error={errors.primary_contact?.[0]}
310
- />
372
+ {mergedConfig.email?.active !== false && (
373
+ <Input
374
+ label="Email"
375
+ type="email"
376
+ isRequired={mergedConfig.email?.mandatory}
377
+ placeholder="Email address"
378
+ value={quickAddEmail}
379
+ onChange={(e) => setQuickAddEmail(e.target.value)}
380
+ error={errors.email?.[0]}
381
+ />
382
+ )}
383
+ {mergedConfig.primary_contact?.active !== false && (
384
+ <Input
385
+ label="Primary Contact"
386
+ isRequired={mergedConfig.primary_contact?.mandatory}
387
+ placeholder="Primary phone number"
388
+ value={quickAddPhone}
389
+ onChange={(e) => setQuickAddPhone(e.target.value)}
390
+ error={errors.primary_contact?.[0]}
391
+ />
392
+ )}
311
393
  </div>
312
394
  <div className="grid grid-cols-2 gap-3">
313
- <Input
314
- label="Secondary Contact"
315
- placeholder="Alternative phone (optional)"
316
- value={quickAddSecondaryContact}
317
- onChange={(e) => setQuickAddSecondaryContact(e.target.value)}
318
- error={errors.secondary_contact?.[0]}
319
- />
320
- <Input
321
- label="Date of Birth"
322
- type="date"
323
- value={quickAddDOB}
324
- onChange={(e) => setQuickAddDOB(e.target.value)}
325
- error={errors.date_of_birth?.[0]}
326
- />
395
+ {mergedConfig.secondary_contact?.active !== false && (
396
+ <Input
397
+ label="Secondary Contact"
398
+ isRequired={mergedConfig.secondary_contact?.mandatory}
399
+ placeholder="Alternative phone (optional)"
400
+ value={quickAddSecondaryContact}
401
+ onChange={(e) => setQuickAddSecondaryContact(e.target.value)}
402
+ error={errors.secondary_contact?.[0]}
403
+ />
404
+ )}
405
+ {mergedConfig.date_of_birth?.active !== false && (
406
+ <Input
407
+ label="Date of Birth"
408
+ type="date"
409
+ isRequired={mergedConfig.date_of_birth?.mandatory}
410
+ value={quickAddDOB}
411
+ onChange={(e) => setQuickAddDOB(e.target.value)}
412
+ error={errors.date_of_birth?.[0]}
413
+ />
414
+ )}
327
415
  </div>
328
- <div className="grid grid-cols-2 gap-3">
329
- <div className="space-y-1">
330
- <label className="text-sm font-semibold text-gray-700">Gender</label>
416
+ {mergedConfig.identity?.active !== false && (
417
+ <div className="space-y-1.5 w-full">
418
+ <label className="text-[13px] font-medium text-[#4A5568]">
419
+ Identity Details {mergedConfig.identity?.mandatory && <span className="text-red-500">*</span>}
420
+ </label>
421
+ <div className="flex relative">
331
422
  <select
332
- value={quickAddGender}
333
- onChange={(e) => setQuickAddGender(e.target.value)}
334
- className="w-full rounded-lg border border-gray-200 px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent bg-white"
423
+ value={quickAddIdentityType}
424
+ onChange={(e) => setQuickAddIdentityType(e.target.value)}
425
+ className={`w-1/3 bg-surface-0 border-[1.5px] ${errors.identity_type ? 'border-danger-alt' : 'border-border-subtle'} rounded-l-[10px] px-3.5 py-2.5 text-[13.5px] text-foreground-1 outline-none transition-all hover:border-gray-300 focus:border-gray-300 focus:bg-surface-1 border-r-0 focus:z-10 appearance-none`}
426
+ style={{ backgroundImage: 'url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23131313%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E")', backgroundRepeat: 'no-repeat', backgroundPosition: 'right .7rem top 50%', backgroundSize: '.65rem auto' }}
335
427
  >
336
- <option value="">Select gender</option>
337
- <option value="male">Male</option>
338
- <option value="female">Female</option>
428
+ <option value="">Select type</option>
429
+ <option value="nic">NIC</option>
430
+ <option value="passport">Passport</option>
431
+ <option value="driving_license">Driving License</option>
432
+ <option value="business_registration">Business Registration</option>
339
433
  <option value="other">Other</option>
340
- <option value="prefer_not_to_say">Prefer not to say</option>
341
434
  </select>
342
- {errors.gender?.[0] && <p className="text-xs text-red-500">{errors.gender[0]}</p>}
435
+ <input
436
+ type="text"
437
+ placeholder="Enter identity number"
438
+ value={quickAddIdentity}
439
+ onChange={(e) => setQuickAddIdentity(e.target.value)}
440
+ className={`flex-1 bg-surface-0 border-[1.5px] ${errors.identity ? 'border-danger-alt' : 'border-border-subtle'} rounded-r-[10px] px-3.5 py-2.5 text-[13.5px] text-foreground-1 outline-none transition-all hover:border-gray-300 focus:border-gray-300 focus:bg-surface-1 placeholder:text-foreground-disabled z-0 focus:z-10 relative`}
441
+ />
343
442
  </div>
443
+ {(errors.identity_type?.[0] || errors.identity?.[0]) && (
444
+ <span className="text-[11px] text-danger-alt font-medium">
445
+ {errors.identity_type?.[0] || errors.identity?.[0]}
446
+ </span>
447
+ )}
448
+ </div>
449
+ )}
450
+ {mergedConfig.address?.active !== false && (
451
+ <div className="space-y-3">
452
+ <div className="flex justify-between items-center">
453
+ <label className="text-[13px] font-medium text-[#4A5568]">
454
+ Address {mergedConfig.address?.mandatory ? <span className="text-red-500">*</span> : "(Optional)"}
455
+ </label>
456
+ <button
457
+ type="button"
458
+ onClick={() => setShowQuickAddDetailedAddress(!showQuickAddDetailedAddress)}
459
+ className="text-xs font-semibold text-primary-600 hover:text-primary-700 transition-colors"
460
+ >
461
+ {showQuickAddDetailedAddress ? "Quick Add Address" : "Add Detailed Address"}
462
+ </button>
463
+ </div>
464
+
465
+ {!showQuickAddDetailedAddress ? (
466
+ <Input
467
+ placeholder="Enter complete primary address..."
468
+ value={quickAddAddressLine1}
469
+ onChange={(e) => setQuickAddAddressLine1(e.target.value)}
470
+ error={errors.address?.[0] || errors['address.address_line_1']?.[0]}
471
+ />
472
+ ) : (
473
+ <div className="p-3 bg-gray-50 border border-gray-100 rounded-xl space-y-3">
474
+ <Input
475
+ label="Address Line 1"
476
+ placeholder="Street address, P.O. box, etc."
477
+ value={quickAddAddressLine1}
478
+ onChange={(e) => setQuickAddAddressLine1(e.target.value)}
479
+ error={errors.address?.[0] || errors['address.address_line_1']?.[0]}
480
+ />
481
+ <Input
482
+ label="Address Line 2"
483
+ placeholder="Apartment, suite, unit, etc. (optional)"
484
+ value={quickAddAddressLine2}
485
+ onChange={(e) => setQuickAddAddressLine2(e.target.value)}
486
+ error={errors['address.address_line_2']?.[0]}
487
+ />
488
+ <div className="grid grid-cols-2 gap-3">
489
+ <Input
490
+ label="City"
491
+ placeholder="City"
492
+ value={quickAddCity}
493
+ onChange={(e) => setQuickAddCity(e.target.value)}
494
+ error={errors['address.city']?.[0]}
495
+ />
496
+ <Input
497
+ label="State / Province"
498
+ placeholder="State or Province"
499
+ value={quickAddState}
500
+ onChange={(e) => setQuickAddState(e.target.value)}
501
+ error={errors['address.state']?.[0]}
502
+ />
503
+ </div>
504
+ <div className="grid grid-cols-2 gap-3">
505
+ <Input
506
+ label="Postal Code"
507
+ placeholder="ZIP or Postal Code"
508
+ value={quickAddPostalCode}
509
+ onChange={(e) => setQuickAddPostalCode(e.target.value)}
510
+ error={errors['address.postal_code']?.[0]}
511
+ />
512
+ <Input
513
+ label="Country"
514
+ placeholder="Country"
515
+ value={quickAddCountry}
516
+ onChange={(e) => setQuickAddCountry(e.target.value)}
517
+ error={errors['address.country']?.[0]}
518
+ />
519
+ </div>
520
+ </div>
521
+ )}
522
+ </div>
523
+ )}
524
+ <div className="grid grid-cols-2 gap-3">
344
525
  {(partyType === "all" || partyType === "customer" || partyType === "supplier") && (
345
526
  <AsyncSearchableSelect
346
527
  label="Default Currency"
@@ -427,6 +427,7 @@ export function ProductSelectionPanel({
427
427
  {searchResults.map((item) => (
428
428
  <div key={item.id} className="flex flex-col border-b border-border-subtle last:border-0">
429
429
  <button
430
+ type="button"
430
431
  onClick={() => {
431
432
  if (item.has_variants) {
432
433
  setExpandedItemIds(prev => prev.includes(item.id) ? prev.filter(id => id !== item.id) : [...prev, item.id]);
@@ -476,7 +477,7 @@ export function ProductSelectionPanel({
476
477
  {formatCurrency(item.variants?.[0]?.[config.priceField === 'default_cost_price' ? 'cost_price' : 'sale_price'] || 0)}
477
478
  </div>
478
479
  )}
479
- {config.showStock && item.stock && (
480
+ {config.showStock && item.has_stocks && item.stock && (
480
481
  <div className={cn(
481
482
  "text-[11px] font-medium",
482
483
  item.stock.qty_available > 0 ? "text-green-600" : "text-danger-alt"
@@ -503,6 +504,7 @@ export function ProductSelectionPanel({
503
504
  <div className="pl-[52px] pr-4 flex flex-col gap-0.5 mt-1">
504
505
  {item.variants?.map((variant: any) => (
505
506
  <button
507
+ type="button"
506
508
  key={variant.id}
507
509
  id={`dropdown-item-variant-${variant.id}`}
508
510
  onClick={() => handleSelectItem(item, variant)}
@@ -527,7 +529,7 @@ export function ProductSelectionPanel({
527
529
  {formatCurrency(variant[config.priceField === 'default_cost_price' ? 'cost_price' : 'sale_price'] || item.variants?.[0]?.[config.priceField === 'default_cost_price' ? 'cost_price' : 'sale_price'] || 0)}
528
530
  </div>
529
531
  )}
530
- {config.showStock && variant.stock && (
532
+ {config.showStock && item.has_stocks && variant.stock && (
531
533
  <div className={cn(
532
534
  "text-[11.5px] font-medium min-w-[50px] text-right",
533
535
  variant.stock.qty_available > 0 ? "text-green-600" : "text-danger-alt"