@apptimate/ui 3.5.0 → 3.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.
Files changed (25) hide show
  1. package/package.json +3 -2
  2. package/src/base-components/Input.tsx +6 -3
  3. package/src/base-components/TagsInput.tsx +106 -0
  4. package/src/base-components/WizardModal.tsx +162 -0
  5. package/src/common-components/item-wizard/ItemFormWizard.tsx +705 -0
  6. package/src/common-components/item-wizard/SkuConfigModal.tsx +314 -0
  7. package/src/common-components/pickers/BrandPicker.tsx +194 -0
  8. package/src/common-components/pickers/CategoryPicker.tsx +342 -0
  9. package/src/common-components/pickers/EntityPickerModal.tsx +221 -0
  10. package/src/common-components/pickers/PartyPicker.tsx +231 -0
  11. package/src/common-components/pickers/TransactionItemPicker.tsx +166 -0
  12. package/src/common-components/pickers/UomGroupPicker.tsx +196 -0
  13. package/src/common-components/pickers/UomPicker.tsx +306 -0
  14. package/src/common-components/pickers/WarehousePicker.tsx +129 -0
  15. package/src/common-components/pickers/index.ts +11 -0
  16. package/src/common-components/pickers/modals/BatchSelectionModal.tsx +359 -0
  17. package/src/common-components/pickers/modals/SerialSelectionModal.tsx +247 -0
  18. package/src/common-components/pickers/modals/types.ts +30 -0
  19. package/src/common-components/transaction/MetadataPanel.tsx +417 -0
  20. package/src/common-components/transaction/ProductSelectionPanel.tsx +587 -0
  21. package/src/common-components/transaction/ProductTransactionScreen.tsx +250 -0
  22. package/src/common-components/transaction/index.ts +17 -0
  23. package/src/common-components/transaction/types.ts +300 -0
  24. package/src/components/shared/ImageUploadComponent.tsx +1 -2
  25. package/src/index.tsx +20 -0
@@ -0,0 +1,250 @@
1
+ 'use client';
2
+
3
+ import React, { useState, useCallback, useMemo } from 'react';
4
+ import { cn } from '@apptimate/core-lib';
5
+ import { ProductSelectionPanel } from './ProductSelectionPanel';
6
+ import { MetadataPanel } from './MetadataPanel';
7
+ import type {
8
+ ProductTransactionScreenProps, TransactionItem, TransactionLineItem,
9
+ TransactionPayload, PaymentEntry, PartyLookup,
10
+ } from './types';
11
+
12
+ /**
13
+ * ProductTransactionScreen — Standardized two-panel transaction UI
14
+ *
15
+ * Left panel (60%): Search/scan → product popup → selected items table
16
+ * Right panel (40%): Party picker, warehouse picker, date, notes, payment modes, submit
17
+ *
18
+ * Configure via `config` prop (use presets: TRANSACTION_PRESETS.sales / .purchase / .transfer / .pos)
19
+ *
20
+ * NOTE: This component does NOT render its own header — the parent page provides the back-arrow + title.
21
+ */
22
+ export function ProductTransactionScreen({
23
+ config,
24
+ defaultWarehouseId,
25
+ onSubmit,
26
+ isSubmitting: externalSubmitting,
27
+ renderExtraMeta,
28
+ }: ProductTransactionScreenProps) {
29
+ // ── State ──
30
+ const [lineItems, setLineItems] = useState<TransactionLineItem[]>([]);
31
+ const [partyId, setPartyId] = useState<number | null>(null);
32
+ const [partyName, setPartyName] = useState<string | null>(null);
33
+ const [warehouseId, setWarehouseId] = useState<number | null>(defaultWarehouseId ?? null);
34
+ const [warehouseName, setWarehouseName] = useState<string | null>(null);
35
+ const [transactionDate, setTransactionDate] = useState<string>(() => {
36
+ return new Date().toISOString().split('T')[0];
37
+ });
38
+ const [notes, setNotes] = useState('');
39
+ const [payments, setPayments] = useState<PaymentEntry[]>([]);
40
+ const [isSubmitting, setIsSubmitting] = useState(false);
41
+
42
+ const submitting = externalSubmitting ?? isSubmitting;
43
+
44
+ // ── Add item to line items ──
45
+ const handleAddItem = useCallback(
46
+ (
47
+ item: TransactionItem,
48
+ variant?: any,
49
+ extra?: { quantity?: number; batches?: any[]; serial_numbers?: string[] }
50
+ ) => {
51
+ // Check if item+variant already exists
52
+ const existingIdx = lineItems.findIndex(
53
+ (l) => l.item.id === item.id && l.variant_id === (variant?.id || null)
54
+ );
55
+
56
+ if (existingIdx >= 0) {
57
+ // Increment quantity
58
+ const updated = [...lineItems];
59
+ const existing = updated[existingIdx];
60
+ const addedQty = extra?.quantity || 1;
61
+ const newQty = existing.quantity + addedQty;
62
+ const discountAmt = (existing.unit_price * newQty * existing.discount_percent) / 100;
63
+
64
+ let mergedBatches = existing.batches;
65
+ if (extra?.batches?.length) {
66
+ mergedBatches = [...(mergedBatches || []), ...extra.batches];
67
+ }
68
+
69
+ let mergedSerials = existing.serial_numbers;
70
+ if (extra?.serial_numbers?.length) {
71
+ mergedSerials = [...(mergedSerials || []), ...extra.serial_numbers];
72
+ }
73
+
74
+ updated[existingIdx] = {
75
+ ...existing,
76
+ quantity: newQty,
77
+ discount_amount: discountAmt,
78
+ line_total: existing.unit_price * newQty - discountAmt,
79
+ batches: mergedBatches,
80
+ serial_numbers: mergedSerials,
81
+ };
82
+ setLineItems(updated);
83
+ return;
84
+ }
85
+
86
+ const variantPriceField = config.priceField === 'default_cost_price' ? 'cost_price' : 'sale_price';
87
+ const actualVariant = variant || (item.variants && item.variants.length > 0 ? item.variants[0] : null);
88
+ const unitPrice = actualVariant ? Number(actualVariant[variantPriceField] || 0) : 0;
89
+ const qty = extra?.quantity || 1;
90
+ const discountAmt = 0; // Initial discount
91
+
92
+ const newLine: TransactionLineItem = {
93
+ uid: `${item.id}-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
94
+ item,
95
+ variant_id: variant?.id || null,
96
+ batches: extra?.batches || [],
97
+ serial_numbers: extra?.serial_numbers || [],
98
+ quantity: qty,
99
+ unit_price: unitPrice,
100
+ discount_percent: 0,
101
+ discount_amount: discountAmt,
102
+ line_total: unitPrice * qty - discountAmt,
103
+ expanded: false,
104
+ };
105
+ setLineItems((prev) => [...prev, newLine]);
106
+ },
107
+ [lineItems, config.priceField]
108
+ );
109
+
110
+ // ── Update line ──
111
+ const handleUpdateLine = useCallback((uid: string, updates: Partial<TransactionLineItem>) => {
112
+ setLineItems((prev) =>
113
+ prev.map((l) => (l.uid === uid ? { ...l, ...updates } : l))
114
+ );
115
+ }, []);
116
+
117
+ // ── Remove line ──
118
+ const handleRemoveLine = useCallback((uid: string) => {
119
+ setLineItems((prev) => prev.filter((l) => l.uid !== uid));
120
+ }, []);
121
+
122
+ // ── Toggle expand ──
123
+ const handleToggleExpand = useCallback((uid: string) => {
124
+ setLineItems((prev) =>
125
+ prev.map((l) => (l.uid === uid ? { ...l, expanded: !l.expanded } : l))
126
+ );
127
+ }, []);
128
+
129
+ // ── Calculations ──
130
+ const subtotal = useMemo(() => lineItems.reduce((s, l) => s + l.unit_price * l.quantity, 0), [lineItems]);
131
+ const discountTotal = useMemo(() => lineItems.reduce((s, l) => s + l.discount_amount, 0), [lineItems]);
132
+ const grandTotal = useMemo(() => subtotal - discountTotal, [subtotal, discountTotal]);
133
+
134
+ // ── Submit handler ──
135
+ const handleSubmit = useCallback(async () => {
136
+ if (lineItems.length === 0) return;
137
+
138
+ const payload: TransactionPayload = {
139
+ type: config.type,
140
+ party_id: partyId,
141
+ warehouse_id: warehouseId!,
142
+ transaction_date: transactionDate,
143
+ notes: notes || undefined,
144
+ lines: lineItems.flatMap((l): TransactionPayload['lines'] => {
145
+ if (l.batches && l.batches.length > 0) {
146
+ return l.batches.map(b => ({
147
+ item_id: l.item.id,
148
+ variant_id: l.variant_id,
149
+ batch_id: b.batch_id,
150
+ batch_number: b.batch_number,
151
+ manufacturing_date: b.manufacturing_date,
152
+ expiry_date: b.expiry_date,
153
+ supplier_reference: b.supplier_reference,
154
+ selling_price: b.selling_price,
155
+ serial_numbers: undefined,
156
+ quantity: b.quantity,
157
+ unit_price: l.unit_price,
158
+ discount_percent: l.discount_percent,
159
+ discount_amount: (l.discount_amount / l.quantity) * b.quantity,
160
+ line_total: (l.line_total / l.quantity) * b.quantity,
161
+ }));
162
+ }
163
+ return [{
164
+ item_id: l.item.id,
165
+ variant_id: l.variant_id,
166
+ batch_id: undefined,
167
+ batch_number: undefined,
168
+ manufacturing_date: undefined,
169
+ expiry_date: undefined,
170
+ supplier_reference: undefined,
171
+ selling_price: undefined,
172
+ serial_numbers: l.serial_numbers,
173
+ quantity: l.quantity,
174
+ unit_price: l.unit_price,
175
+ discount_percent: l.discount_percent,
176
+ discount_amount: l.discount_amount,
177
+ line_total: l.line_total,
178
+ }];
179
+ }),
180
+ payments: config.showPayment ? payments : undefined,
181
+ subtotal,
182
+ discount_total: discountTotal,
183
+ grand_total: grandTotal,
184
+ };
185
+
186
+ setIsSubmitting(true);
187
+ try {
188
+ await onSubmit(payload);
189
+ // Reset on success
190
+ setLineItems([]);
191
+ setPayments([]);
192
+ setNotes('');
193
+ setTransactionDate(new Date().toISOString().split('T')[0]);
194
+ if (!defaultWarehouseId) { setWarehouseId(null); setWarehouseName(null); }
195
+ } catch (error) {
196
+ // The parent usually displays a toast error. We catch it here
197
+ // to stop the loading state and prevent clearing the form.
198
+ // Intentionally not logging with console.error to avoid Next.js dev overlay.
199
+ console.warn("Transaction submission failed.", error instanceof Error ? error.message : "");
200
+ } finally {
201
+ setIsSubmitting(false);
202
+ }
203
+ }, [lineItems, config, partyId, warehouseId, transactionDate, notes, payments, subtotal, discountTotal, grandTotal, onSubmit, defaultWarehouseId]);
204
+
205
+ return (
206
+ <div className="flex flex-col h-full">
207
+ {/* ── Two-Panel Layout ── */}
208
+ <div className="flex-1 flex gap-5 min-h-0">
209
+ {/* Left: Product Selection (75%) */}
210
+ <div className="flex-[3] min-w-0 flex flex-col">
211
+ <ProductSelectionPanel
212
+ config={config}
213
+ warehouseId={warehouseId ?? undefined}
214
+ lineItems={lineItems}
215
+ onAddItem={handleAddItem}
216
+ onUpdateLine={handleUpdateLine}
217
+ onRemoveLine={handleRemoveLine}
218
+ onToggleExpand={handleToggleExpand}
219
+ />
220
+ </div>
221
+
222
+ {/* Divider */}
223
+ <div className="w-px bg-border-subtle/60 shrink-0" />
224
+
225
+ {/* Right: Metadata (25%) */}
226
+ <div className="flex-1 min-w-0 flex flex-col overflow-y-auto custom-scrollbar pr-1">
227
+ <MetadataPanel
228
+ config={config}
229
+ lineItems={lineItems}
230
+ partyId={partyId}
231
+ partyName={partyName}
232
+ warehouseId={warehouseId}
233
+ warehouseName={warehouseName}
234
+ transactionDate={transactionDate}
235
+ notes={notes}
236
+ payments={payments}
237
+ onPartyChange={(id, party) => { setPartyId(id); setPartyName(party?.name ?? null); }}
238
+ onWarehouseChange={(id, name) => { setWarehouseId(id); setWarehouseName(name ?? null); }}
239
+ onDateChange={setTransactionDate}
240
+ onNotesChange={setNotes}
241
+ onPaymentsChange={setPayments}
242
+ onSubmit={handleSubmit}
243
+ isSubmitting={submitting}
244
+ renderExtraMeta={renderExtraMeta}
245
+ />
246
+ </div>
247
+ </div>
248
+ </div>
249
+ );
250
+ }
@@ -0,0 +1,17 @@
1
+ export { ProductTransactionScreen } from './ProductTransactionScreen';
2
+ export { ProductSelectionPanel } from './ProductSelectionPanel';
3
+ export { MetadataPanel } from './MetadataPanel';
4
+ export { TRANSACTION_PRESETS } from './types';
5
+ export type {
6
+ ProductTransactionScreenProps,
7
+ TransactionScreenConfig,
8
+ TransactionPayload,
9
+ TransactionLineItem,
10
+ TransactionItem,
11
+ TransactionType,
12
+ PaymentEntry,
13
+ PaymentMode,
14
+ BankAccount,
15
+ WarehouseLookup,
16
+ PartyLookup,
17
+ } from './types';
@@ -0,0 +1,300 @@
1
+ // ─── Product Transaction Screen — Shared Types ──────────────────────────────
2
+
3
+ // ── Item & Product Types ──
4
+
5
+ export interface TransactionItem {
6
+ id: number;
7
+ name: string;
8
+ sku: string;
9
+ barcode?: string;
10
+ image_url?: string;
11
+ category_id?: number;
12
+ brand_id?: number;
13
+ uom_id?: number;
14
+ has_variants: boolean;
15
+ has_stocks: boolean;
16
+ tracking_type: 'none' | 'batch' | 'serial';
17
+ default_sale_price: number;
18
+ default_cost_price: number;
19
+ is_discountable: boolean;
20
+ category?: { id: number; name: string };
21
+ brand?: { id: number; name: string };
22
+ uom?: { id: number; name: string; abbreviation: string };
23
+ variants?: TransactionItemVariant[];
24
+ primary_image?: { id: number; url: string } | null;
25
+ stock?: { qty_on_hand: number; qty_allocated: number; qty_available: number };
26
+ }
27
+
28
+ export interface TransactionItemVariant {
29
+ id: number;
30
+ item_id: number;
31
+ variant_name: string;
32
+ sku: string;
33
+ cost_price?: number;
34
+ sale_price?: number;
35
+ additional_price?: number;
36
+ }
37
+
38
+ export interface TransactionBatch {
39
+ id: number;
40
+ item_id: number;
41
+ batch_number: string;
42
+ manufacturing_date?: string;
43
+ expiry_date?: string;
44
+ supplier_reference?: string;
45
+ selling_price?: number;
46
+ qty_available?: number;
47
+ qty_on_hand?: number;
48
+ }
49
+
50
+ export interface TransactionLineBatch {
51
+ batch_id?: number | null;
52
+ batch_number?: string;
53
+ manufacturing_date?: string;
54
+ expiry_date?: string;
55
+ supplier_reference?: string;
56
+ selling_price?: number;
57
+ quantity: number;
58
+ }
59
+
60
+ // ── Selected Line Item (added to cart) ──
61
+
62
+ export interface TransactionLineItem {
63
+ uid: string; // Client-side unique key for React
64
+ item: TransactionItem;
65
+ variant_id?: number | null;
66
+ batches?: TransactionLineBatch[];
67
+ serial_numbers?: string[];
68
+ quantity: number;
69
+ unit_price: number;
70
+ discount_percent: number;
71
+ discount_amount: number;
72
+ line_total: number;
73
+ notes?: string;
74
+ expanded?: boolean; // Whether the detail row is expanded
75
+ }
76
+
77
+ import type { PaymentModeField, PaymentMode, PaymentEntry, BankAccount } from '../PaymentSection';
78
+ export type { PaymentModeField, PaymentMode, PaymentEntry, BankAccount };
79
+
80
+ // ── Warehouse & Party ──
81
+
82
+ export interface WarehouseLookup {
83
+ id: number;
84
+ name: string;
85
+ code: string;
86
+ }
87
+
88
+ export interface PartyLookup {
89
+ id: number;
90
+ name: string;
91
+ code?: string;
92
+ type?: string;
93
+ phone?: string;
94
+ email?: string;
95
+ }
96
+
97
+ // ── Component Props ──
98
+
99
+ export type PriceField = 'default_sale_price' | 'default_cost_price';
100
+ export type TransactionType = 'sales' | 'purchase' | 'transfer' | 'pos' | 'opening_stock' | 'quotation' | 'grn';
101
+
102
+ export interface TransactionScreenConfig {
103
+ /** Module type — controls behaviour & price field */
104
+ type: TransactionType;
105
+ /** Which price field to use */
106
+ priceField: PriceField;
107
+ /** Show price column in the items table */
108
+ showPrices: boolean;
109
+ /** Enable discount inputs */
110
+ allowDiscount: boolean;
111
+ /** Show payment section */
112
+ showPayment: boolean;
113
+ /** Party label */
114
+ partyLabel: string;
115
+ /** Party type filter */
116
+ partyType: 'customer' | 'supplier' | 'all';
117
+ /** Show warehouse selector */
118
+ showWarehouse: boolean;
119
+ /** Show notes */
120
+ showNotes: boolean;
121
+ /** Submit button label */
122
+ submitLabel: string;
123
+ /** Page title */
124
+ title: string;
125
+ /** When true, the "party" slot shows a WarehousePicker instead of a PartyPicker */
126
+ partyIsWarehouse?: boolean;
127
+ /** Whether to show the party selector at all (defaults to true) */
128
+ showParty?: boolean;
129
+ /** Allow creating new batches and serials during selection */
130
+ allowCreateBatchAndSerial?: boolean;
131
+ /** If true, user must select a warehouse before searching/adding items */
132
+ requireWarehouseFirst?: boolean;
133
+ /** Allow editing unit price inline */
134
+ allowEditPrice?: boolean;
135
+ /** Whether to show available stock for variants in the search dropdown */
136
+ showStock?: boolean;
137
+ }
138
+
139
+ // ── Module Presets ──
140
+
141
+ export const TRANSACTION_PRESETS: Record<TransactionType, TransactionScreenConfig> = {
142
+ sales: {
143
+ type: 'sales',
144
+ priceField: 'default_sale_price',
145
+ showPrices: true,
146
+ allowDiscount: true,
147
+ showPayment: false,
148
+ partyLabel: 'Customer',
149
+ partyType: 'customer',
150
+ showWarehouse: true,
151
+ showNotes: true,
152
+ submitLabel: 'Create Sales Order',
153
+ title: 'New Sale',
154
+ allowCreateBatchAndSerial: false,
155
+ allowEditPrice: false,
156
+ showStock: true,
157
+ },
158
+ quotation: {
159
+ type: 'quotation',
160
+ priceField: 'default_sale_price',
161
+ showPrices: true,
162
+ allowDiscount: true,
163
+ showPayment: false,
164
+ partyLabel: 'Customer',
165
+ partyType: 'customer',
166
+ showWarehouse: true,
167
+ showNotes: true,
168
+ submitLabel: 'Create Quotation',
169
+ title: 'New Quotation',
170
+ allowCreateBatchAndSerial: false,
171
+ allowEditPrice: false,
172
+ showStock: true,
173
+ },
174
+ purchase: {
175
+ type: 'purchase',
176
+ priceField: 'default_cost_price',
177
+ showPrices: true,
178
+ allowDiscount: true,
179
+ showPayment: false,
180
+ partyLabel: 'Supplier',
181
+ partyType: 'supplier',
182
+ showWarehouse: true,
183
+ showNotes: true,
184
+ submitLabel: 'Create Purchase Order',
185
+ title: 'New Purchase',
186
+ allowCreateBatchAndSerial: true,
187
+ allowEditPrice: true,
188
+ showStock: true,
189
+ },
190
+ transfer: {
191
+ type: 'transfer',
192
+ priceField: 'default_sale_price',
193
+ showPrices: false,
194
+ allowDiscount: false,
195
+ showPayment: false,
196
+ partyLabel: 'Destination Warehouse',
197
+ partyType: 'all',
198
+ partyIsWarehouse: true,
199
+ showWarehouse: true,
200
+ showNotes: true,
201
+ submitLabel: 'Create Transfer',
202
+ title: 'New Transfer',
203
+ allowCreateBatchAndSerial: false,
204
+ allowEditPrice: false,
205
+ showStock: true,
206
+ requireWarehouseFirst: true,
207
+ },
208
+ pos: {
209
+ type: 'pos',
210
+ priceField: 'default_sale_price',
211
+ showPrices: true,
212
+ allowDiscount: true,
213
+ showPayment: true,
214
+ partyLabel: 'Customer',
215
+ partyType: 'customer',
216
+ showWarehouse: false,
217
+ showNotes: false,
218
+ submitLabel: 'Complete Sale',
219
+ title: 'POS Checkout',
220
+ allowCreateBatchAndSerial: false,
221
+ allowEditPrice: false,
222
+ showStock: true,
223
+ },
224
+ opening_stock: {
225
+ type: 'opening_stock',
226
+ priceField: 'default_cost_price',
227
+ showPrices: true,
228
+ allowDiscount: false,
229
+ showPayment: false,
230
+ partyLabel: '',
231
+ partyType: 'all',
232
+ showParty: false,
233
+ showWarehouse: true,
234
+ showNotes: true,
235
+ submitLabel: 'Create Opening Stock',
236
+ title: 'Opening Stock',
237
+ allowCreateBatchAndSerial: true,
238
+ allowEditPrice: true,
239
+ showStock: true,
240
+ },
241
+ grn: {
242
+ type: 'grn',
243
+ priceField: 'default_cost_price',
244
+ showPrices: true,
245
+ allowDiscount: true,
246
+ showPayment: false,
247
+ partyLabel: 'Supplier',
248
+ partyType: 'supplier',
249
+ showWarehouse: true,
250
+ showNotes: true,
251
+ submitLabel: 'Create Direct GRN',
252
+ title: 'New Direct GRN',
253
+ allowCreateBatchAndSerial: true,
254
+ allowEditPrice: true,
255
+ showStock: true,
256
+ },
257
+ };
258
+
259
+ // ── Props for the main component ──
260
+
261
+ export interface ProductTransactionScreenProps {
262
+ /** Module preset config */
263
+ config: TransactionScreenConfig;
264
+ /** Default warehouse ID (POS pre-selects, others allow picking) */
265
+ defaultWarehouseId?: number;
266
+ /** Callback on successful submit */
267
+ onSubmit: (payload: TransactionPayload) => Promise<void>;
268
+ /** Optional loading state override */
269
+ isSubmitting?: boolean;
270
+ /** Optional extra content rendered at the top of the metadata (right) panel */
271
+ renderExtraMeta?: () => React.ReactNode;
272
+ }
273
+
274
+ export interface TransactionPayload {
275
+ type: TransactionType;
276
+ party_id?: number | null;
277
+ warehouse_id: number;
278
+ transaction_date?: string;
279
+ notes?: string;
280
+ lines: Array<{
281
+ item_id: number;
282
+ variant_id?: number | null;
283
+ batch_id?: number | null;
284
+ batch_number?: string;
285
+ manufacturing_date?: string;
286
+ expiry_date?: string;
287
+ supplier_reference?: string;
288
+ selling_price?: number;
289
+ serial_numbers?: string[];
290
+ quantity: number;
291
+ unit_price: number;
292
+ discount_percent: number;
293
+ discount_amount: number;
294
+ line_total: number;
295
+ }>;
296
+ payments?: PaymentEntry[];
297
+ subtotal: number;
298
+ discount_total: number;
299
+ grand_total: number;
300
+ }
@@ -75,7 +75,6 @@ export default function ImageUploadComponent({
75
75
  };
76
76
 
77
77
  const uploadUrl = buildProxyUrl(apiPath.replace("{id}", String(entityId)));
78
- const multipleUploadUrl = `${uploadUrl}/multiple`;
79
78
 
80
79
  const getAuthHeaders = (): Record<string, string> => {
81
80
  const headers: Record<string, string> = {};
@@ -113,7 +112,7 @@ export default function ImageUploadComponent({
113
112
  const formData = new FormData();
114
113
  fileArray.forEach(f => formData.append("images[]", f));
115
114
  Object.entries(extraFormData).forEach(([k, v]) => formData.append(k, v));
116
- const resp = await fetch(multipleUploadUrl, { method: "POST", headers: getAuthHeaders(), body: formData });
115
+ const resp = await fetch(uploadUrl, { method: "POST", headers: getAuthHeaders(), body: formData });
117
116
  const data = await resp.json();
118
117
  if (data.is_success) { toast.success(`${fileArray.length} images uploaded`); onRefresh(); } else toast.error(data.message || "Upload failed");
119
118
  }
package/src/index.tsx CHANGED
@@ -34,3 +34,23 @@ export * from './common-components/DashboardLayout';
34
34
  export * from './common-components/charts';
35
35
  export * from './common-components/PaymentSection';
36
36
  export * from './base-components/FinanceErrorToast';
37
+ export * from './base-components/TagsInput';
38
+ export * from './base-components/WizardModal';
39
+
40
+ // Pickers
41
+ export * from './common-components/pickers/BrandPicker';
42
+ export * from './common-components/pickers/CategoryPicker';
43
+ export * from './common-components/pickers/EntityPickerModal';
44
+ export * from './common-components/pickers/UomGroupPicker';
45
+ export * from './common-components/pickers/UomPicker';
46
+ export * from './common-components/pickers/PartyPicker';
47
+ export * from './common-components/pickers/WarehousePicker';
48
+
49
+ // Item Wizard
50
+ export { default as ItemFormWizard } from './common-components/item-wizard/ItemFormWizard';
51
+ export * from './common-components/item-wizard/ItemFormWizard';
52
+ export { default as SkuConfigModal } from './common-components/item-wizard/SkuConfigModal';
53
+ export * from './common-components/item-wizard/SkuConfigModal';
54
+
55
+ // Transaction
56
+ export * from './common-components/transaction';