@apptimate/ui 6.0.0 → 6.2.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 +1 -1
- package/src/common-components/item-wizard/ItemFormWizard.tsx +35 -16
- package/src/common-components/item-wizard/QuickItemWizard.tsx +86 -0
- package/src/common-components/print/TemplateRenderer.tsx +27 -6
- package/src/common-components/transaction/ProductSelectionPanel.tsx +19 -13
- package/src/common-components/transaction/ProductTransactionScreen.tsx +2 -0
- package/src/common-components/transaction/types.ts +2 -0
- package/src/index.tsx +1 -0
package/package.json
CHANGED
|
@@ -22,7 +22,7 @@ export interface ItemFormData {
|
|
|
22
22
|
category_id: string; category_name: string;
|
|
23
23
|
brand_id: string; brand_name: string;
|
|
24
24
|
description: string;
|
|
25
|
-
sale_price: string; cost_price: string;
|
|
25
|
+
sale_price: string; cost_price: string; min_sales_price: string;
|
|
26
26
|
has_variants: boolean; tracking_type: string; valuation_method: string;
|
|
27
27
|
// Page 2: Stock & Config
|
|
28
28
|
uom_id: string; uom_name: string;
|
|
@@ -41,7 +41,7 @@ export interface ItemFormData {
|
|
|
41
41
|
|
|
42
42
|
export interface VariantRow {
|
|
43
43
|
id?: number; sku: string; variant_name: string; barcode: string;
|
|
44
|
-
sale_price: string; cost_price: string; attribute_values: Record<string, string>;
|
|
44
|
+
sale_price: string; cost_price: string; min_sales_price: string; attribute_values: Record<string, string>;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
export const emptyFormData: ItemFormData = {
|
|
@@ -49,7 +49,7 @@ export const emptyFormData: ItemFormData = {
|
|
|
49
49
|
category_id: "", category_name: "",
|
|
50
50
|
brand_id: "", brand_name: "",
|
|
51
51
|
description: "",
|
|
52
|
-
sale_price: "0", cost_price: "0",
|
|
52
|
+
sale_price: "0", cost_price: "0", min_sales_price: "0",
|
|
53
53
|
has_variants: false, tracking_type: "none", valuation_method: "fifo",
|
|
54
54
|
uom_id: "", uom_name: "", purchase_uom_id: "", sales_uom_id: "",
|
|
55
55
|
purchase_uom_conversion: "1", sales_uom_conversion: "1",
|
|
@@ -473,7 +473,7 @@ export default function ItemFormWizard({
|
|
|
473
473
|
const addVariantRow = () => {
|
|
474
474
|
setFormData((p) => ({
|
|
475
475
|
...p,
|
|
476
|
-
variants: [...p.variants, { sku: "", variant_name: "", barcode: "", sale_price: "", cost_price: "", attribute_values: {} }],
|
|
476
|
+
variants: [...p.variants, { sku: "", variant_name: "", barcode: "", sale_price: "", cost_price: "", min_sales_price: "", attribute_values: {} }],
|
|
477
477
|
}));
|
|
478
478
|
};
|
|
479
479
|
|
|
@@ -524,6 +524,7 @@ export default function ItemFormWizard({
|
|
|
524
524
|
barcode: "",
|
|
525
525
|
sale_price: formData.sale_price || "0",
|
|
526
526
|
cost_price: formData.cost_price || "0",
|
|
527
|
+
min_sales_price: formData.min_sales_price || "0",
|
|
527
528
|
attribute_values: combo,
|
|
528
529
|
};
|
|
529
530
|
});
|
|
@@ -543,13 +544,16 @@ export default function ItemFormWizard({
|
|
|
543
544
|
{!formData.has_variants ? (
|
|
544
545
|
<div className="p-4 bg-gray-50/50 rounded-xl border border-gray-100">
|
|
545
546
|
<p className="text-[11px] font-bold text-gray-400 uppercase tracking-wider mb-3">Item Pricing</p>
|
|
546
|
-
<div className="grid grid-cols-
|
|
547
|
+
<div className="grid grid-cols-3 gap-4">
|
|
547
548
|
{formData.is_available_sell && (
|
|
548
549
|
<Input label="Sale Price" type="number" placeholder="0.00" value={formData.sale_price} onChange={(e) => update("sale_price", e.target.value)} />
|
|
549
550
|
)}
|
|
550
551
|
{formData.is_available_purchase && (
|
|
551
552
|
<Input label="Cost Price" type="number" placeholder="0.00" value={formData.cost_price} onChange={(e) => update("cost_price", e.target.value)} />
|
|
552
553
|
)}
|
|
554
|
+
{formData.is_available_sell && (
|
|
555
|
+
<Input label="Min Sales Price" type="number" placeholder="0.00" value={formData.min_sales_price} onChange={(e) => update("min_sales_price", e.target.value)} />
|
|
556
|
+
)}
|
|
553
557
|
</div>
|
|
554
558
|
</div>
|
|
555
559
|
) : (
|
|
@@ -577,14 +581,24 @@ export default function ItemFormWizard({
|
|
|
577
581
|
</th>
|
|
578
582
|
<th className="px-4 py-3 font-medium align-top">Barcode</th>
|
|
579
583
|
{formData.is_available_sell && (
|
|
580
|
-
|
|
581
|
-
<
|
|
582
|
-
<
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
584
|
+
<>
|
|
585
|
+
<th className="px-4 py-3 font-medium w-[140px] align-top">
|
|
586
|
+
<div className="flex flex-col items-start gap-1">
|
|
587
|
+
<span>Sale Price</span>
|
|
588
|
+
{formData.variants.length > 1 && (
|
|
589
|
+
<button type="button" onClick={() => copyToAll("sale_price")} className="text-primary-600 hover:text-primary-700 font-semibold normal-case text-[11px]">Copy to All</button>
|
|
590
|
+
)}
|
|
591
|
+
</div>
|
|
592
|
+
</th>
|
|
593
|
+
<th className="px-4 py-3 font-medium w-[140px] align-top">
|
|
594
|
+
<div className="flex flex-col items-start gap-1">
|
|
595
|
+
<span>Min Sale Price</span>
|
|
596
|
+
{formData.variants.length > 1 && (
|
|
597
|
+
<button type="button" onClick={() => copyToAll("min_sales_price")} className="text-primary-600 hover:text-primary-700 font-semibold normal-case text-[11px]">Copy to All</button>
|
|
598
|
+
)}
|
|
599
|
+
</div>
|
|
600
|
+
</th>
|
|
601
|
+
</>
|
|
588
602
|
)}
|
|
589
603
|
{formData.is_available_purchase && (
|
|
590
604
|
<th className="px-4 py-3 font-medium w-[140px] align-top">
|
|
@@ -612,9 +626,14 @@ export default function ItemFormWizard({
|
|
|
612
626
|
<Input placeholder="Leave empty to auto-generate" value={v.barcode} onChange={(e) => updateVariant(idx, "barcode", e.target.value)} />
|
|
613
627
|
</td>
|
|
614
628
|
{formData.is_available_sell && (
|
|
615
|
-
|
|
616
|
-
<
|
|
617
|
-
|
|
629
|
+
<>
|
|
630
|
+
<td className="p-2 min-w-[120px] align-top">
|
|
631
|
+
<Input type="number" placeholder="0.00" value={v.sale_price} onChange={(e) => updateVariant(idx, "sale_price", e.target.value)} />
|
|
632
|
+
</td>
|
|
633
|
+
<td className="p-2 min-w-[120px] align-top">
|
|
634
|
+
<Input type="number" placeholder="0.00" value={v.min_sales_price} onChange={(e) => updateVariant(idx, "min_sales_price", e.target.value)} />
|
|
635
|
+
</td>
|
|
636
|
+
</>
|
|
618
637
|
)}
|
|
619
638
|
{formData.is_available_purchase && (
|
|
620
639
|
<td className="p-2 min-w-[120px] align-top">
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
import ItemFormWizard from "./ItemFormWizard";
|
|
5
|
+
import { emptyFormData } from "./ItemFormWizard";
|
|
6
|
+
import { createItem } from "@apptimate/core-lib";
|
|
7
|
+
import toast from "react-hot-toast";
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
isOpen: boolean;
|
|
11
|
+
onClose: () => void;
|
|
12
|
+
onSuccess?: (newItem: any) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function QuickItemWizard({ isOpen, onClose, onSuccess }: Props) {
|
|
16
|
+
const [formData, setFormData] = useState<any>(emptyFormData);
|
|
17
|
+
const [uoms, setUoms] = useState<any[]>([]);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (isOpen) {
|
|
21
|
+
setFormData(emptyFormData);
|
|
22
|
+
fetchLookups();
|
|
23
|
+
}
|
|
24
|
+
}, [isOpen]);
|
|
25
|
+
|
|
26
|
+
const fetchLookups = async () => {
|
|
27
|
+
try {
|
|
28
|
+
const { lookupUoms } = await import("@apptimate/core-lib");
|
|
29
|
+
const uomRes = await lookupUoms();
|
|
30
|
+
if (uomRes.is_success) setUoms(uomRes.result || []);
|
|
31
|
+
} catch { }
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const handleSubmit = async () => {
|
|
35
|
+
try {
|
|
36
|
+
if (!formData.name || !formData.sku || !formData.category_id || !formData.uom_id) {
|
|
37
|
+
toast.error("Please fill required fields (Name, SKU, Category, UOM)");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const payload: any = {
|
|
42
|
+
...formData,
|
|
43
|
+
sale_price: parseFloat(formData.sale_price) || 0,
|
|
44
|
+
cost_price: parseFloat(formData.cost_price) || 0,
|
|
45
|
+
min_stock_level: formData.min_stock_level ? parseFloat(formData.min_stock_level) : null,
|
|
46
|
+
max_stock_level: formData.max_stock_level ? parseFloat(formData.max_stock_level) : null,
|
|
47
|
+
reorder_point: formData.reorder_point ? parseFloat(formData.reorder_point) : null,
|
|
48
|
+
reorder_qty: formData.reorder_qty ? parseFloat(formData.reorder_qty) : null,
|
|
49
|
+
lead_time_days: formData.lead_time_days ? parseInt(formData.lead_time_days) : null,
|
|
50
|
+
weight: formData.weight ? parseFloat(formData.weight) : null,
|
|
51
|
+
variants: formData.has_variants ? formData.variants.map((v: any) => ({
|
|
52
|
+
...v, sale_price: v.sale_price ? parseFloat(v.sale_price) : null,
|
|
53
|
+
cost_price: v.cost_price ? parseFloat(v.cost_price) : null,
|
|
54
|
+
})) : undefined,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
delete payload.category_name;
|
|
58
|
+
delete payload.brand_name;
|
|
59
|
+
|
|
60
|
+
const res = await createItem(payload);
|
|
61
|
+
if (res.is_success) {
|
|
62
|
+
toast.success(res.message || "Item created successfully");
|
|
63
|
+
onClose();
|
|
64
|
+
if (onSuccess && res.result) {
|
|
65
|
+
onSuccess(res.result);
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
toast.error(res.message || "Failed to create item");
|
|
69
|
+
}
|
|
70
|
+
} catch (e: any) {
|
|
71
|
+
toast.error(e.message || "An error occurred while creating item");
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<ItemFormWizard
|
|
77
|
+
isOpen={isOpen}
|
|
78
|
+
onClose={onClose}
|
|
79
|
+
formData={formData}
|
|
80
|
+
setFormData={setFormData}
|
|
81
|
+
uoms={uoms}
|
|
82
|
+
onSubmit={handleSubmit}
|
|
83
|
+
isEditing={false}
|
|
84
|
+
/>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
@@ -213,6 +213,7 @@ export function replaceTokens(text: string, entityData: any): string {
|
|
|
213
213
|
if ((val === undefined || val === null) && path.trim() === "inventory_item_variants.sku") val = data.variants?.[0]?.sku || data.sku || data.code;
|
|
214
214
|
if ((val === undefined || val === null) && path.trim() === "inventory_item_variants.barcode") val = data.variants?.[0]?.barcode || data.barcode || data.sku || data.code;
|
|
215
215
|
if ((val === undefined || val === null) && (path.trim() === "inventory_item_variants.sale_price" || path.trim() === "sale_price" || path.trim() === "price")) val = data.sale_price || data.price || data.variants?.[0]?.sale_price || 0;
|
|
216
|
+
if ((val === undefined || val === null) && (path.trim() === "inventory_item_variants.min_sales_price" || path.trim() === "min_sales_price")) val = data.min_sales_price || data.variants?.[0]?.min_sales_price || 0;
|
|
216
217
|
if ((val === undefined || val === null) && (path.trim() === "inventory_item_variants.cost_price" || path.trim() === "cost_price")) val = data.cost_price || data.variants?.[0]?.cost_price || 0;
|
|
217
218
|
if ((val === undefined || val === null) && path.trim() === "inventory_batches.batch_number") val = data.batch_number || data.batch?.batch_number || "";
|
|
218
219
|
if ((val === undefined || val === null) && path.trim() === "inventory_batches.selling_price") val = data.price || data.selling_price || data.batch?.selling_price || data.sale_price || 0;
|
|
@@ -347,6 +348,15 @@ function LineRenderer({ line, defaultFont, defaultColor, defaultFontSize, entity
|
|
|
347
348
|
wordBreak: "break-word" as const,
|
|
348
349
|
};
|
|
349
350
|
|
|
351
|
+
const ec = typeof line.extra_config === 'string' ? JSON.parse(line.extra_config || '{}') : (line.extra_config || {});
|
|
352
|
+
|
|
353
|
+
if (ec.truncate_single_line) {
|
|
354
|
+
baseStyle.whiteSpace = "nowrap";
|
|
355
|
+
baseStyle.overflow = "hidden";
|
|
356
|
+
baseStyle.textOverflow = "ellipsis";
|
|
357
|
+
baseStyle.display = "block";
|
|
358
|
+
}
|
|
359
|
+
|
|
350
360
|
if (line.border_style && line.border_style !== "none") {
|
|
351
361
|
const bw = `${line.border_width || 1}px`;
|
|
352
362
|
const bc = line.border_color || "#000000";
|
|
@@ -360,7 +370,11 @@ function LineRenderer({ line, defaultFont, defaultColor, defaultFontSize, entity
|
|
|
360
370
|
|
|
361
371
|
// Text
|
|
362
372
|
if (line.line_type === "text") {
|
|
363
|
-
|
|
373
|
+
const content = replaceTokens(line.static_text || "", entityData).trim();
|
|
374
|
+
if (ec.truncate_single_line) {
|
|
375
|
+
return <div style={baseStyle}>{content}</div>;
|
|
376
|
+
}
|
|
377
|
+
return <div style={baseStyle}>{content.split("\n").map((t, i) => <div key={i}>{t || <br />}</div>)}</div>;
|
|
364
378
|
}
|
|
365
379
|
|
|
366
380
|
// Token (legacy)
|
|
@@ -405,11 +419,14 @@ function LineRenderer({ line, defaultFont, defaultColor, defaultFontSize, entity
|
|
|
405
419
|
const childCols = line.extra_config?.columns || line.columns || [];
|
|
406
420
|
return (
|
|
407
421
|
<div style={{ display: "flex", marginTop: `${line.spacing_before || 0}px`, marginBottom: `${line.spacing_after || 0}px`, backgroundColor: line.background_color || "transparent", padding: line.padding || "0", borderRadius: line.border_radius || "0" }}>
|
|
408
|
-
{childCols.map((col: any, i: number) =>
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
422
|
+
{childCols.map((col: any, i: number) => {
|
|
423
|
+
const isTruncated = col.truncate_single_line || ec.truncate_single_line;
|
|
424
|
+
return (
|
|
425
|
+
<div key={i} style={{ width: `${col.width_percent || 50}%`, fontFamily: `${col.font_family || line.font_family || defaultFont}, sans-serif`, fontSize: `${col.font_size ? Number(col.font_size) : (line.font_size ? Number(line.font_size) : defaultFontSize)}px`, lineHeight: 1.15, fontWeight: col.font_weight === "bold" ? 700 : col.font_weight === "light" ? 300 : (line.font_weight === "bold" ? 700 : line.font_weight === "light" ? 300 : (isA4 === false ? 600 : 400)), color: col.font_color || line.font_color || defaultColor, textAlign: (col.text_align || col.align || "left") as any, fontStyle: col.is_italic ? "italic" : "normal", textDecoration: col.is_underline ? "underline" : "none", textTransform: (col.text_transform || "none") as any, backgroundColor: col.background_color || "transparent", padding: col.padding || "0", whiteSpace: isTruncated ? "nowrap" : "pre-wrap", overflow: isTruncated ? "hidden" : "visible", textOverflow: isTruncated ? "ellipsis" : "clip", display: "block" }}>
|
|
426
|
+
{isTruncated ? replaceTokens(col.content || col.static_text || "", entityData) : replaceTokens(col.content || col.static_text || "", entityData).split("\n").map((t, ii) => <div key={ii}>{t || <br />}</div>)}
|
|
427
|
+
</div>
|
|
428
|
+
);
|
|
429
|
+
})}
|
|
413
430
|
</div>
|
|
414
431
|
);
|
|
415
432
|
}
|
|
@@ -560,6 +577,10 @@ function LineRenderer({ line, defaultFont, defaultColor, defaultFontSize, entity
|
|
|
560
577
|
let num = 0;
|
|
561
578
|
if (path === 'inventory_item_variants.cost_price' || path === 'cost_price') {
|
|
562
579
|
num = Number(entityData.cost_price || entityData.variants?.[0]?.cost_price || 0);
|
|
580
|
+
} else if (path === 'inventory_item_variants.min_sales_price' || path === 'min_sales_price') {
|
|
581
|
+
num = Number(entityData.min_sales_price || entityData.variants?.[0]?.min_sales_price || 0);
|
|
582
|
+
} else if (path === 'inventory_item_variants.sale_price' || path === 'sale_price' || path === 'price') {
|
|
583
|
+
num = Number(entityData.sale_price || entityData.price || entityData.variants?.[0]?.sale_price || 0);
|
|
563
584
|
} else {
|
|
564
585
|
let val = entityData;
|
|
565
586
|
for (const k of path.split(".")) {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
|
4
4
|
import { cn } from '@apptimate/core-lib';
|
|
5
5
|
import {
|
|
6
|
-
Search, ScanBarcode, X, ShoppingCart, Package, Loader2,
|
|
6
|
+
Search, ScanBarcode, X, ShoppingCart, Package, Loader2, Plus,
|
|
7
7
|
Trash2, ChevronDown, ChevronRight, AlertTriangle, Tag
|
|
8
8
|
} from 'lucide-react';
|
|
9
9
|
import { Badge } from '../../base-components/Badge';
|
|
@@ -24,6 +24,7 @@ interface ProductSelectionPanelProps {
|
|
|
24
24
|
onUpdateLine: (uid: string, updates: Partial<TransactionLineItem>) => void;
|
|
25
25
|
onRemoveLine: (uid: string) => void;
|
|
26
26
|
onToggleExpand: (uid: string) => void;
|
|
27
|
+
onAddNewItem?: (callbacks: { setItemSearch: (value: string) => void }) => void;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
export function ProductSelectionPanel({
|
|
@@ -35,6 +36,7 @@ export function ProductSelectionPanel({
|
|
|
35
36
|
onUpdateLine,
|
|
36
37
|
onRemoveLine,
|
|
37
38
|
onToggleExpand,
|
|
39
|
+
onAddNewItem,
|
|
38
40
|
}: ProductSelectionPanelProps) {
|
|
39
41
|
const [searchValue, setSearchValue] = useState('');
|
|
40
42
|
const [searchResults, setSearchResults] = useState<TransactionItem[]>([]);
|
|
@@ -400,18 +402,22 @@ export function ProductSelectionPanel({
|
|
|
400
402
|
</button>
|
|
401
403
|
}
|
|
402
404
|
/>
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
405
|
+
{onAddNewItem && (
|
|
406
|
+
<>
|
|
407
|
+
<div className="w-[1px] h-5 bg-border-subtle mx-1" />
|
|
408
|
+
<button
|
|
409
|
+
type="button"
|
|
410
|
+
onClick={() => onAddNewItem({ setItemSearch: handleSearch })}
|
|
411
|
+
title="Create new item"
|
|
412
|
+
className="flex items-center justify-center outline-none mr-1"
|
|
413
|
+
>
|
|
414
|
+
<Plus
|
|
415
|
+
size={18}
|
|
416
|
+
className="text-foreground-disabled shrink-0 cursor-pointer hover:text-primary transition-colors"
|
|
417
|
+
/>
|
|
418
|
+
</button>
|
|
419
|
+
</>
|
|
420
|
+
)}
|
|
415
421
|
</div>
|
|
416
422
|
</div>
|
|
417
423
|
|
|
@@ -30,6 +30,7 @@ export function ProductTransactionScreen({
|
|
|
30
30
|
renderExtraMeta,
|
|
31
31
|
fetchChequeLeaves,
|
|
32
32
|
initialData,
|
|
33
|
+
onAddNewItem,
|
|
33
34
|
}: ProductTransactionScreenProps) {
|
|
34
35
|
// ── State ──
|
|
35
36
|
const [lineItems, setLineItems] = useState<TransactionLineItem[]>([]);
|
|
@@ -469,6 +470,7 @@ export function ProductTransactionScreen({
|
|
|
469
470
|
onUpdateLine={handleUpdateLine}
|
|
470
471
|
onRemoveLine={handleRemoveLine}
|
|
471
472
|
onToggleExpand={handleToggleExpand}
|
|
473
|
+
onAddNewItem={onAddNewItem}
|
|
472
474
|
/>
|
|
473
475
|
</div>
|
|
474
476
|
|
|
@@ -316,6 +316,8 @@ export interface ProductTransactionScreenProps {
|
|
|
316
316
|
fetchChequeLeaves?: (query: string, page: number) => Promise<{ is_success: boolean; result?: any[] }>;
|
|
317
317
|
/** Initial data for populating the transaction screen (e.g., from a PO) */
|
|
318
318
|
initialData?: any;
|
|
319
|
+
/** Optional callback to create a new item directly from the search bar */
|
|
320
|
+
onAddNewItem?: (callbacks: { setItemSearch: (value: string) => void }) => void;
|
|
319
321
|
}
|
|
320
322
|
|
|
321
323
|
export interface TransactionPayload {
|
package/src/index.tsx
CHANGED
|
@@ -54,6 +54,7 @@ export * from './common-components/pickers/WarehousePicker';
|
|
|
54
54
|
// Item Wizard
|
|
55
55
|
export { default as ItemFormWizard } from './common-components/item-wizard/ItemFormWizard';
|
|
56
56
|
export * from './common-components/item-wizard/ItemFormWizard';
|
|
57
|
+
export * from './common-components/item-wizard/QuickItemWizard';
|
|
57
58
|
export { default as SkuConfigModal } from './common-components/item-wizard/SkuConfigModal';
|
|
58
59
|
export * from './common-components/item-wizard/SkuConfigModal';
|
|
59
60
|
|