@apptimate/ui 6.4.0 → 6.5.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
|
@@ -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(
|
|
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
|
|
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=
|
|
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
|
|
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
|
|
226
|
-
title={isEditing
|
|
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
|
-
|
|
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
|
|
237
|
-
title={isEditing
|
|
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
|
-
|
|
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
|
|
261
|
-
title={isEditing
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
303
|
-
title={isEditing
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
436
|
-
<
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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
|
|
647
|
-
title={isEditing
|
|
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
|
|
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
|
)}
|
|
@@ -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"
|