@deenruv/admin-dashboard 1.0.6 → 1.0.11
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/dist/DeenruvAdminPanel.js +5 -4
- package/dist/DeenruvDeveloperIndicator.js +1 -1
- package/dist/components/ContentAreaSkeleton.js +5 -0
- package/dist/components/Menu/LanguagesDropdown.js +4 -3
- package/dist/components/Menu/Navigation.js +10 -9
- package/dist/components/Menu/Notifications.js +2 -1
- package/dist/components/Menu/flag-icons.js +16 -0
- package/dist/components/Menu/index.js +3 -2
- package/dist/pages/Custom404.js +16 -2
- package/dist/pages/Root.js +2 -2
- package/dist/pages/admins/Detail.js +7 -7
- package/dist/pages/admins/_components/AdminDetailView.js +17 -9
- package/dist/pages/channels/Detail.js +12 -12
- package/dist/pages/channels/_components/ChannelDetailView.js +25 -17
- package/dist/pages/collections/Detail.js +8 -7
- package/dist/pages/collections/List.js +1 -2
- package/dist/pages/collections/_components/CollectionDetailView.js +65 -22
- package/dist/pages/collections/_components/CollectionProductVariantsDrawer.js +1 -1
- package/dist/pages/countries/Detail.js +5 -5
- package/dist/pages/countries/_components/CountryDetailView.js +15 -8
- package/dist/pages/customer-groups/Detail.js +3 -3
- package/dist/pages/customer-groups/_components/CustomerGroupsDetailView.js +4 -4
- package/dist/pages/customers/Detail.js +6 -8
- package/dist/pages/customers/_components/AddressForm.js +48 -60
- package/dist/pages/customers/_components/AddressesCard.js +1 -1
- package/dist/pages/customers/_components/CustomerDetailView.js +9 -9
- package/dist/pages/dashboard/Dashboard.js +3 -2
- package/dist/pages/extensions/Extensions.js +12 -13
- package/dist/pages/facets/Detail.js +6 -6
- package/dist/pages/facets/_components/AddFacetValueDialog.js +39 -39
- package/dist/pages/facets/_components/FacetDetailView.js +19 -12
- package/dist/pages/global-settings/GlobalSettingsComponent.js +9 -8
- package/dist/pages/global-settings/index.js +4 -4
- package/dist/pages/orders/List.js +2 -1
- package/dist/pages/orders/_components/AddressCard.js +123 -78
- package/dist/pages/orders/_components/CustomerSelectCard.js +38 -43
- package/dist/pages/orders/_components/FulfillmentModal.js +47 -28
- package/dist/pages/orders/_components/ModifyingCard.js +30 -13
- package/dist/pages/orders/_components/OrderLineCustomFields.js +2 -1
- package/dist/pages/orders/_components/PossibleOrderStates.js +2 -1
- package/dist/pages/orders/_components/ProductsCard.js +2 -1
- package/dist/pages/orders/_components/SurchargeCard.js +58 -60
- package/dist/pages/orders/_components/TopActions.js +2 -1
- package/dist/pages/payment-methods/Detail.js +8 -7
- package/dist/pages/payment-methods/_components/PaymentMethodDetailView.js +22 -14
- package/dist/pages/products/Detail.js +9 -7
- package/dist/pages/products/_components/AddOptionGroupDialog.js +36 -14
- package/dist/pages/products/_components/BasicFieldsCard.js +5 -2
- package/dist/pages/products/_components/OptionValueCard.js +25 -23
- package/dist/pages/products/_components/ProductDetailSidebar.js +15 -7
- package/dist/pages/products/_components/ProductDetailView.js +51 -17
- package/dist/pages/products/_components/Variant.js +105 -57
- package/dist/pages/promotions/Detail.js +13 -11
- package/dist/pages/promotions/_components/OptionsCard.js +1 -1
- package/dist/pages/promotions/_components/PromotionDetailSidebar.js +9 -6
- package/dist/pages/promotions/_components/PromotionDetailView.js +26 -20
- package/dist/pages/roles/Detail.js +5 -5
- package/dist/pages/roles/_components/RoleDetailView.js +13 -8
- package/dist/pages/sellers/Detail.js +4 -4
- package/dist/pages/sellers/_components/SellerDetailView.js +4 -4
- package/dist/pages/shipping-methods/Detail.js +14 -12
- package/dist/pages/shipping-methods/_components/Lines.js +1 -1
- package/dist/pages/shipping-methods/_components/ShippingMethodDetailView.js +26 -13
- package/dist/pages/shipping-methods/_components/TestCard.js +67 -21
- package/dist/pages/status/_components/Health.js +2 -1
- package/dist/pages/status/_components/Jobs.js +2 -6
- package/dist/pages/stock-locations/Detail.js +4 -4
- package/dist/pages/stock-locations/_components/StockLocationDetailView.js +5 -5
- package/dist/pages/tax-categories/Detail.js +4 -4
- package/dist/pages/tax-categories/_components/TaxCategoryDetailView.js +7 -7
- package/dist/pages/tax-rates/Detail.js +8 -8
- package/dist/pages/tax-rates/_components/TaxRateDetailView.js +13 -9
- package/dist/pages/zones/Detail.js +4 -4
- package/dist/pages/zones/_components/ZoneDetailView.js +8 -7
- package/dist/version.js +1 -1
- package/package.json +79 -78
|
@@ -1,64 +1,54 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { SurchargeTable } from './';
|
|
4
|
-
import { useTranslation, Button, Checkbox, Input, Label,
|
|
4
|
+
import { useTranslation, Button, Checkbox, Input, Label, useDeenruvForm, z, useOrder, CustomCard, } from '@deenruv/react-ui-devkit';
|
|
5
5
|
import { useCallback, useState } from 'react';
|
|
6
6
|
import { PlusCircle, DollarSign, Tag, FileText, Percent, Receipt, Loader2 } from 'lucide-react';
|
|
7
|
+
const surchargeSchema = z.object({
|
|
8
|
+
description: z.string().min(1),
|
|
9
|
+
sku: z.string().min(1),
|
|
10
|
+
price: z.number().positive(),
|
|
11
|
+
priceIncludesTax: z.boolean().default(false),
|
|
12
|
+
taxDescription: z.string().default(''),
|
|
13
|
+
taxRate: z.number().default(0),
|
|
14
|
+
});
|
|
7
15
|
export const SurchargeCard = () => {
|
|
8
16
|
const { t } = useTranslation('orders');
|
|
9
17
|
const { setModifyOrderInput, modifyOrderInput, setModifiedOrder, modifiedOrder } = useOrder();
|
|
10
18
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
initialValue: '',
|
|
21
|
-
validate: (v) => {
|
|
22
|
-
if (!v || v === '')
|
|
23
|
-
return [t('surcharge.validation.skuRequired', 'SKU is required')];
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
price: {
|
|
27
|
-
initialValue: 0,
|
|
28
|
-
validate: (v) => {
|
|
29
|
-
if (v === undefined || v === null)
|
|
30
|
-
return [t('surcharge.validation.priceRequired', 'Price is required')];
|
|
31
|
-
if (v <= 0)
|
|
32
|
-
return [t('surcharge.validation.pricePositive', 'Price must be greater than zero')];
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
priceIncludesTax: {
|
|
36
|
-
initialValue: false,
|
|
37
|
-
},
|
|
38
|
-
taxDescription: {
|
|
39
|
-
initialValue: '',
|
|
40
|
-
},
|
|
41
|
-
taxRate: {
|
|
42
|
-
initialValue: 0,
|
|
19
|
+
const form = useDeenruvForm({
|
|
20
|
+
schema: surchargeSchema,
|
|
21
|
+
defaultValues: {
|
|
22
|
+
description: '',
|
|
23
|
+
sku: '',
|
|
24
|
+
price: 0,
|
|
25
|
+
priceIncludesTax: false,
|
|
26
|
+
taxDescription: '',
|
|
27
|
+
taxRate: 0,
|
|
43
28
|
},
|
|
44
29
|
});
|
|
30
|
+
const description = form.watch('description');
|
|
31
|
+
const sku = form.watch('sku');
|
|
32
|
+
const price = form.watch('price');
|
|
33
|
+
const priceIncludesTax = form.watch('priceIncludesTax');
|
|
34
|
+
const taxDescription = form.watch('taxDescription');
|
|
35
|
+
const taxRate = form.watch('taxRate');
|
|
45
36
|
const handleAddSurcharge = useCallback(async () => {
|
|
46
37
|
// Validate all fields
|
|
47
|
-
if (!
|
|
38
|
+
if (!description || !sku || !price || price <= 0) {
|
|
48
39
|
return;
|
|
49
40
|
}
|
|
50
41
|
setIsSubmitting(true);
|
|
51
42
|
try {
|
|
52
43
|
const orderWithSurcharge = Object.assign({}, modifyOrderInput);
|
|
53
|
-
const { description, price, priceIncludesTax, sku, taxDescription, taxRate } = state;
|
|
54
44
|
const surchargesArray = modifyOrderInput?.surcharges || [];
|
|
55
45
|
surchargesArray.push({
|
|
56
|
-
description: description
|
|
57
|
-
price
|
|
58
|
-
priceIncludesTax: priceIncludesTax
|
|
59
|
-
sku
|
|
60
|
-
taxDescription
|
|
61
|
-
taxRate
|
|
46
|
+
description: description || '',
|
|
47
|
+
price,
|
|
48
|
+
priceIncludesTax: priceIncludesTax || false,
|
|
49
|
+
sku,
|
|
50
|
+
taxDescription,
|
|
51
|
+
taxRate,
|
|
62
52
|
});
|
|
63
53
|
orderWithSurcharge.surcharges = surchargesArray;
|
|
64
54
|
if (modifiedOrder)
|
|
@@ -67,33 +57,41 @@ export const SurchargeCard = () => {
|
|
|
67
57
|
surcharges: [
|
|
68
58
|
...modifiedOrder.surcharges,
|
|
69
59
|
{
|
|
70
|
-
description: description
|
|
71
|
-
price: taxRate
|
|
72
|
-
sku
|
|
60
|
+
description: description || '',
|
|
61
|
+
price: taxRate && priceIncludesTax ? price / (1 + taxRate / 100) : price,
|
|
62
|
+
sku,
|
|
73
63
|
createdAt: new Date().toDateString(),
|
|
74
|
-
priceWithTax: taxRate
|
|
75
|
-
|
|
76
|
-
: +price?.value,
|
|
77
|
-
taxRate: taxRate?.value || 0,
|
|
64
|
+
priceWithTax: taxRate && !priceIncludesTax ? +price * (+taxRate / 100) + +price : +price,
|
|
65
|
+
taxRate: taxRate || 0,
|
|
78
66
|
},
|
|
79
67
|
],
|
|
80
68
|
});
|
|
81
69
|
setModifyOrderInput(orderWithSurcharge);
|
|
82
70
|
// Reset form fields
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
71
|
+
form.reset({
|
|
72
|
+
description: '',
|
|
73
|
+
sku: '',
|
|
74
|
+
price: 0,
|
|
75
|
+
priceIncludesTax: false,
|
|
76
|
+
taxDescription: '',
|
|
77
|
+
taxRate: 0,
|
|
78
|
+
});
|
|
89
79
|
}
|
|
90
80
|
finally {
|
|
91
81
|
setIsSubmitting(false);
|
|
92
82
|
}
|
|
93
|
-
}, [
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
83
|
+
}, [
|
|
84
|
+
description,
|
|
85
|
+
sku,
|
|
86
|
+
price,
|
|
87
|
+
priceIncludesTax,
|
|
88
|
+
taxDescription,
|
|
89
|
+
taxRate,
|
|
90
|
+
modifiedOrder,
|
|
91
|
+
modifyOrderInput,
|
|
92
|
+
setModifiedOrder,
|
|
93
|
+
setModifyOrderInput,
|
|
94
|
+
form,
|
|
95
|
+
]);
|
|
96
|
+
return (_jsx(CustomCard, { color: "yellow", description: t('surcharge.description', 'Add additional fees or charges to this order'), title: t('surcharge.title', 'Order Surcharges'), icon: _jsx(Receipt, { className: "size-5 text-yellow-500 dark:text-yellow-400" }), bottomRight: _jsx(Button, { onClick: handleAddSurcharge, disabled: isSubmitting || !description || !sku || !price || price <= 0, className: "ml-auto gap-2", children: isSubmitting ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "size-4 animate-spin" }), t('surcharge.processing', 'Processing...')] })) : (_jsxs(_Fragment, { children: [_jsx(PlusCircle, { className: "size-4" }), t('surcharge.addButton', 'Add Surcharge')] })) }), children: _jsxs("div", { className: "space-y-6", children: [_jsx(SurchargeTable, {}), _jsx("div", { children: _jsxs("div", { className: "mb-4 grid grid-cols-1 gap-4 md:grid-cols-3 lg:grid-cols-5", children: [_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { htmlFor: "surcharge-description", className: "text-sm font-medium", children: [t('surcharge.labels.description', 'Description'), " ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsxs("div", { className: "relative", children: [_jsx(Input, { id: "surcharge-description", placeholder: t('surcharge.placeholders.description', 'Enter surcharge description'), value: description || '', onChange: (e) => form.setField('description', e.target.value), className: "pl-9", errors: form.formState.errors.description?.message ? [form.formState.errors.description.message] : undefined }), _jsx("div", { className: "absolute top-1/2 left-3 -translate-y-1/2 text-muted-foreground", children: _jsx(FileText, { className: "size-4" }) })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { htmlFor: "surcharge-sku", className: "text-sm font-medium", children: [t('surcharge.labels.sku', 'SKU'), " ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsxs("div", { className: "relative", children: [_jsx(Input, { id: "surcharge-sku", placeholder: t('surcharge.placeholders.sku', 'Enter SKU code'), value: sku || '', onChange: (e) => form.setField('sku', e.target.value), className: "pl-9", errors: form.formState.errors.sku?.message ? [form.formState.errors.sku.message] : undefined }), _jsx("div", { className: "absolute top-1/2 left-3 -translate-y-1/2 text-muted-foreground", children: _jsx(Tag, { className: "size-4" }) })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { htmlFor: "surcharge-price", className: "text-sm font-medium", children: [t('surcharge.labels.price', 'Price'), " ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsxs("div", { className: "relative", children: [_jsx(Input, { id: "surcharge-price", type: "currency", endAdornment: modifiedOrder?.currencyCode, step: "0.01", min: "0", placeholder: t('surcharge.placeholders.price', 'Enter price'), value: price || '', onChange: (e) => form.setField('price', +e.target.value), className: "pl-9", errors: form.formState.errors.price?.message ? [form.formState.errors.price.message] : undefined }), _jsx("div", { className: "absolute top-1/2 left-3 -translate-y-1/2 text-muted-foreground", children: _jsx(DollarSign, { className: "size-4" }) })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "surcharge-tax-rate", className: "text-sm font-medium", children: t('surcharge.labels.taxRate', 'Tax Rate (%)') }), _jsxs("div", { className: "relative", children: [_jsx(Input, { id: "surcharge-tax-rate", type: "number", step: "0.01", min: "0", placeholder: t('surcharge.placeholders.taxRate', 'Enter tax rate'), value: taxRate || 0, onChange: (e) => form.setField('taxRate', +e.target.value), className: "pl-9" }), _jsx("div", { className: "absolute top-1/2 left-3 -translate-y-1/2 text-muted-foreground", children: _jsx(Percent, { className: "size-4" }) })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "surcharge-tax-description", className: "text-sm font-medium", children: t('surcharge.labels.taxDescription', 'Tax Description') }), _jsxs("div", { className: "relative", children: [_jsx(Input, { id: "surcharge-tax-description", placeholder: t('surcharge.placeholders.taxDescription', 'Enter tax description'), value: taxDescription || '', onChange: (e) => form.setField('taxDescription', e.target.value), className: "pl-9" }), _jsx("div", { className: "absolute top-1/2 left-3 -translate-y-1/2 text-muted-foreground", children: _jsx(FileText, { className: "size-4" }) })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "surcharge-tax-included", className: "text-sm font-medium", children: t('surcharge.labels.includesTax', { value: taxRate }) }), _jsxs("div", { className: "flex items-center gap-2 pt-2", children: [_jsx(Checkbox, { id: "surcharge-tax-included", checked: priceIncludesTax || false, onCheckedChange: (e) => form.setField('priceIncludesTax', Boolean(e)) }), _jsx(Label, { htmlFor: "surcharge-tax-included", className: "cursor-pointer text-sm", children: t('surcharge.placeholders.taxIncluded', 'Tax is included in the price') })] })] })] }) })] }) }));
|
|
99
97
|
};
|
|
@@ -6,6 +6,7 @@ import { PossibleOrderStates } from "./PossibleOrderStates";
|
|
|
6
6
|
import { DeletionResult, HistoryEntryType } from '@deenruv/admin-types';
|
|
7
7
|
import { ChevronLeft, EllipsisVerticalIcon, Info } from 'lucide-react';
|
|
8
8
|
import { useMemo, useState } from 'react';
|
|
9
|
+
import { useShallow } from 'zustand/react/shallow';
|
|
9
10
|
import { useNavigate } from 'react-router';
|
|
10
11
|
import { toast } from 'sonner';
|
|
11
12
|
import { ORDER_STATE } from "../../../graphql/base";
|
|
@@ -14,7 +15,7 @@ import { CancelAndRefundDialog, ModifyAcceptModal } from './index.js';
|
|
|
14
15
|
import React from 'react';
|
|
15
16
|
export const TopActions = () => {
|
|
16
17
|
const { currentPossibilities, manualChange, setManualChange, fetchOrderHistory, fetchOrder, order, changeOrderState, cancelOrder: _cancelOrder, cancelAndRefundOrder, } = useOrder();
|
|
17
|
-
const orderProcess = useServer((p) => p.serverConfig?.orderProcess || []);
|
|
18
|
+
const orderProcess = useServer(useShallow((p) => p.serverConfig?.orderProcess || []));
|
|
18
19
|
const { t } = useTranslation('orders');
|
|
19
20
|
const navigate = useNavigate();
|
|
20
21
|
const { getDetailViewActions } = usePluginStore();
|
|
@@ -14,22 +14,23 @@ export const PaymentMethodsDetailPage = () => {
|
|
|
14
14
|
const { t } = useTranslation('paymentMethods');
|
|
15
15
|
const { translationsValidator, stringValidator, configurableOperationValidator } = useValidators();
|
|
16
16
|
const onSubmitHandler = useCallback((data) => {
|
|
17
|
-
if (!data.code
|
|
17
|
+
if (!data.code) {
|
|
18
18
|
throw new Error('Name is required.');
|
|
19
19
|
}
|
|
20
|
+
const checker = data.checker;
|
|
20
21
|
const inputData = {
|
|
21
|
-
code: data.code
|
|
22
|
-
handler: data.handler
|
|
23
|
-
enabled: data.enabled
|
|
24
|
-
translations: data.translations
|
|
25
|
-
...(data.customFields
|
|
22
|
+
code: data.code,
|
|
23
|
+
handler: data.handler,
|
|
24
|
+
enabled: (data.enabled ?? false),
|
|
25
|
+
translations: data.translations,
|
|
26
|
+
...(data.customFields ? { customFields: data.customFields } : {}),
|
|
26
27
|
};
|
|
27
28
|
if (id) {
|
|
28
29
|
return update({
|
|
29
30
|
input: {
|
|
30
31
|
id,
|
|
31
32
|
...inputData,
|
|
32
|
-
checker:
|
|
33
|
+
checker: checker?.code !== '' ? checker : undefined,
|
|
33
34
|
},
|
|
34
35
|
});
|
|
35
36
|
}
|
|
@@ -13,8 +13,7 @@ const PAYMENT_METHOD_FORM_KEYS = [
|
|
|
13
13
|
];
|
|
14
14
|
export const PaymentMethodDetailView = () => {
|
|
15
15
|
const { form, fetchEntity, entity, id } = useDetailView('paymentMethods-detail-view', ...PAYMENT_METHOD_FORM_KEYS);
|
|
16
|
-
const { base
|
|
17
|
-
// const editMode = useMemo(() => !!id, [id]);
|
|
16
|
+
const { base } = form;
|
|
18
17
|
const { t } = useTranslation('paymentMethods');
|
|
19
18
|
const { translationsLanguage: currentTranslationLng } = useSettings();
|
|
20
19
|
useEffect(() => {
|
|
@@ -22,32 +21,41 @@ export const PaymentMethodDetailView = () => {
|
|
|
22
21
|
const res = await fetchEntity();
|
|
23
22
|
if (!res)
|
|
24
23
|
return;
|
|
25
|
-
setField('code', res.code);
|
|
26
|
-
setField('enabled', res.enabled);
|
|
27
|
-
setField('translations', res.translations);
|
|
28
|
-
setField('handler', {
|
|
24
|
+
base.setField('code', res.code);
|
|
25
|
+
base.setField('enabled', res.enabled);
|
|
26
|
+
base.setField('translations', res.translations);
|
|
27
|
+
base.setField('handler', {
|
|
29
28
|
arguments: res.handler.args,
|
|
30
29
|
code: res.handler.code,
|
|
31
30
|
});
|
|
32
|
-
setField('checker', {
|
|
31
|
+
base.setField('checker', {
|
|
33
32
|
arguments: res.checker?.args || [],
|
|
34
33
|
code: res.checker?.code || '',
|
|
35
34
|
});
|
|
36
35
|
})();
|
|
37
36
|
}, []);
|
|
38
|
-
const translations =
|
|
37
|
+
const translations = base.watch('translations') || [];
|
|
39
38
|
const currentTranslationValue = translations.find((v) => v.languageCode === currentTranslationLng);
|
|
40
39
|
const setTranslationField = useCallback((field, e) => {
|
|
41
|
-
|
|
40
|
+
// Merge with existing translation to preserve other fields (name, description, etc.)
|
|
41
|
+
const baseTranslation = currentTranslationValue ?? {
|
|
42
|
+
languageCode: currentTranslationLng,
|
|
43
|
+
name: '',
|
|
44
|
+
description: '',
|
|
45
|
+
};
|
|
46
|
+
base.setField('translations', setInArrayBy(translations, (t) => t.languageCode === currentTranslationLng, {
|
|
47
|
+
...baseTranslation,
|
|
42
48
|
[field]: e,
|
|
43
49
|
languageCode: currentTranslationLng,
|
|
44
50
|
}));
|
|
45
|
-
}, [currentTranslationLng, translations]);
|
|
46
|
-
return (_jsx("main", { className: "my-4", children: _jsxs("div", { className: "flex flex-col gap-3", children: [_jsx(CustomCard, { title: t('details.basic.title'), icon: _jsx(CardIcons.basic, {}), color: "green", children: _jsxs("div", { className: "flex flex-wrap items-start gap-4 p-0 pt-4", children: [_jsxs("div", { className: "flex w-full flex-wrap items-start gap-4 p-0 pt-4 xl:flex-nowrap", children: [_jsx("div", { className: "flex basis-full md:basis-1/3", children: _jsx(Input, { label: t('details.basic.name'), value: currentTranslationValue?.name ??
|
|
47
|
-
|
|
51
|
+
}, [currentTranslationLng, translations, currentTranslationValue]);
|
|
52
|
+
return (_jsx("main", { className: "my-4", children: _jsxs("div", { className: "flex flex-col gap-3", children: [_jsx(CustomCard, { title: t('details.basic.title'), icon: _jsx(CardIcons.basic, {}), color: "green", children: _jsxs("div", { className: "flex flex-wrap items-start gap-4 p-0 pt-4", children: [_jsxs("div", { className: "flex w-full flex-wrap items-start gap-4 p-0 pt-4 xl:flex-nowrap", children: [_jsx("div", { className: "flex basis-full md:basis-1/3", children: _jsx(Input, { label: t('details.basic.name'), value: currentTranslationValue?.name ?? '', onChange: (e) => setTranslationField('name', e.target.value), errors: base.formState.errors?.translations?.message
|
|
53
|
+
? [base.formState.errors.translations.message]
|
|
54
|
+
: undefined, required: true }) }), _jsx("div", { className: "flex basis-full md:basis-1/3", children: _jsx(Input, { label: t('details.basic.code'), value: base.watch('code') ?? '', onChange: (e) => base.setField('code', e.target.value), errors: base.formState.errors?.code?.message ? [base.formState.errors.code.message] : undefined, required: true }) }), _jsxs("div", { className: "mt-7 flex basis-full items-center gap-3 md:basis-1/3", children: [_jsx(Switch, { checked: base.watch('enabled') ?? false, onCheckedChange: (e) => base.setField('enabled', e) }), _jsx(Label, { children: t('details.basic.enabled') })] })] }), _jsxs("div", { className: "flex basis-full flex-col", children: [_jsx(Label, { className: "mb-2", children: t('details.basic.description') }), _jsx(RichTextEditor, { content: currentTranslationValue?.description ?? '', onContentChanged: (e) => setTranslationField('description', e) })] })] }) }), _jsx(DetailViewMarker, { position: 'paymentMethods-detail-view' }), _jsx(EntityCustomFields, { entityName: "paymentMethod", id: id, hideButton: true, onChange: (customFields, translations) => {
|
|
55
|
+
base.setField('customFields', customFields);
|
|
48
56
|
if (translations)
|
|
49
|
-
setField('translations', translations);
|
|
57
|
+
base.setField('translations', translations);
|
|
50
58
|
}, initialValues: entity && 'customFields' in entity
|
|
51
59
|
? { customFields: entity.customFields, translations: entity.translations }
|
|
52
|
-
: { customFields: {} } }), _jsx(OptionsCard, { currentHandlerValue:
|
|
60
|
+
: { customFields: {} } }), _jsx(OptionsCard, { currentHandlerValue: base.watch('handler') ?? undefined, currentCheckerValue: base.watch('checker') ?? undefined, onHandlerValueChange: (handler) => base.setField('handler', handler), onCheckerValueChange: (checker) => base.setField('checker', checker), handlerErrors: base.formState.errors?.handler?.message ? [base.formState.errors.handler.message] : undefined })] }) }));
|
|
53
61
|
};
|
|
@@ -41,6 +41,8 @@ export const ProductsDetailPage = () => {
|
|
|
41
41
|
config: {
|
|
42
42
|
translations: {
|
|
43
43
|
validate: (v) => {
|
|
44
|
+
if (!v || !v.length)
|
|
45
|
+
return [t('validation.nameSlugRequired')];
|
|
44
46
|
const { name, slug } = v[0];
|
|
45
47
|
if (!name || !slug)
|
|
46
48
|
return [t('validation.nameSlugRequired')];
|
|
@@ -51,14 +53,14 @@ export const ProductsDetailPage = () => {
|
|
|
51
53
|
if (!data.translations)
|
|
52
54
|
throw new Error('Name is required.');
|
|
53
55
|
const input = {
|
|
54
|
-
translations: data.translations
|
|
55
|
-
assetIds: data.assetIds
|
|
56
|
-
featuredAssetId: data.featuredAssetId
|
|
57
|
-
facetValueIds: data.facetValueIds
|
|
58
|
-
enabled: data.enabled
|
|
59
|
-
...(data.customFields
|
|
56
|
+
translations: data.translations,
|
|
57
|
+
assetIds: data.assetIds,
|
|
58
|
+
featuredAssetId: data.featuredAssetId,
|
|
59
|
+
facetValueIds: data.facetValueIds,
|
|
60
|
+
enabled: data.enabled,
|
|
61
|
+
...(data.customFields ? { customFields: data.customFields } : {}),
|
|
60
62
|
};
|
|
61
|
-
return id ? update({ input: { id, ...input } }) : create({ input });
|
|
63
|
+
return id ? update({ input: { id, ...input } }) : create({ input: input });
|
|
62
64
|
},
|
|
63
65
|
onDeleted: () => {
|
|
64
66
|
if (id)
|
|
@@ -1,23 +1,45 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Button, DialogHeader, DialogTitle, Input, Dialog, DialogContent, DialogTrigger, Label, DialogFooter, apiClient,
|
|
2
|
+
import { Button, DialogHeader, DialogTitle, Input, Dialog, DialogContent, DialogTrigger, Label, DialogFooter, apiClient, setInArrayBy, EntityCustomFields, useTranslation, useDeenruvForm, z, } from '@deenruv/react-ui-devkit';
|
|
3
3
|
import { useCallback, useState } from 'react';
|
|
4
4
|
import { toast } from 'sonner';
|
|
5
|
+
const addOptionGroupSchema = z.object({
|
|
6
|
+
code: z.string().default(''),
|
|
7
|
+
translations: z
|
|
8
|
+
.array(z
|
|
9
|
+
.object({
|
|
10
|
+
name: z.string(),
|
|
11
|
+
languageCode: z.string(),
|
|
12
|
+
})
|
|
13
|
+
.passthrough())
|
|
14
|
+
.default([]),
|
|
15
|
+
customFields: z.record(z.string(), z.unknown()).optional().default({}),
|
|
16
|
+
});
|
|
5
17
|
export const AddOptionGroupDialog = ({ currentTranslationLng, productId, onSuccess, }) => {
|
|
6
18
|
const { t } = useTranslation('products');
|
|
7
19
|
const [open, setOpen] = useState(false);
|
|
8
|
-
const
|
|
9
|
-
|
|
20
|
+
const form = useDeenruvForm({
|
|
21
|
+
schema: addOptionGroupSchema,
|
|
22
|
+
defaultValues: {
|
|
23
|
+
code: '',
|
|
24
|
+
translations: [],
|
|
25
|
+
customFields: {},
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
const translations = form.watch('translations') || [];
|
|
10
29
|
const [codeEditedManually, setCodeEditedManually] = useState(false);
|
|
11
30
|
const createGroup = useCallback(() => {
|
|
12
|
-
|
|
31
|
+
const code = form.getValues('code');
|
|
32
|
+
const currentTranslations = form.getValues('translations');
|
|
33
|
+
const customFields = form.getValues('customFields');
|
|
34
|
+
if (code && currentTranslations)
|
|
13
35
|
return apiClient('mutation')({
|
|
14
36
|
createProductOptionGroup: [
|
|
15
37
|
{
|
|
16
38
|
input: {
|
|
17
|
-
code
|
|
39
|
+
code,
|
|
18
40
|
options: [],
|
|
19
|
-
translations:
|
|
20
|
-
...(
|
|
41
|
+
translations: currentTranslations,
|
|
42
|
+
...(customFields && Object.keys(customFields).length > 0 ? { customFields } : {}),
|
|
21
43
|
},
|
|
22
44
|
},
|
|
23
45
|
{
|
|
@@ -47,19 +69,19 @@ export const AddOptionGroupDialog = ({ currentTranslationLng, productId, onSucce
|
|
|
47
69
|
.catch(() => {
|
|
48
70
|
toast(t('toasts.createOptionGroupErrorToast'));
|
|
49
71
|
});
|
|
50
|
-
}, [
|
|
51
|
-
return (_jsxs(Dialog, { open: open, onOpenChange: setOpen, children: [_jsx(DialogTrigger, { asChild: true, children: _jsx(Button, { className: "self-end", children: t('addOptionGroupDialog.title') }) }), _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: t('addOptionGroupDialog.title') }) }), _jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs("div", { children: [_jsx(Label, { children: t('addOptionGroupDialog.name') }), _jsx(Input, { className: "mt-1", value:
|
|
52
|
-
setField('translations', setInArrayBy(translations, (t) => t.languageCode
|
|
72
|
+
}, [form, productId, onSuccess, t]);
|
|
73
|
+
return (_jsxs(Dialog, { open: open, onOpenChange: setOpen, children: [_jsx(DialogTrigger, { asChild: true, children: _jsx(Button, { className: "self-end", children: t('addOptionGroupDialog.title') }) }), _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: t('addOptionGroupDialog.title') }) }), _jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs("div", { children: [_jsx(Label, { children: t('addOptionGroupDialog.name') }), _jsx(Input, { className: "mt-1", value: translations[0]?.name ?? undefined, onChange: (e) => {
|
|
74
|
+
form.setField('translations', setInArrayBy(translations, (t) => t.languageCode === currentTranslationLng, {
|
|
53
75
|
name: e.target.value,
|
|
54
76
|
languageCode: currentTranslationLng,
|
|
55
77
|
}));
|
|
56
78
|
if (!codeEditedManually) {
|
|
57
|
-
setField('code', e.target.value.replace(/\s+/g, '-'));
|
|
79
|
+
form.setField('code', e.target.value.replace(/\s+/g, '-'));
|
|
58
80
|
}
|
|
59
|
-
} })] }), _jsxs("div", { children: [_jsx(Label, { children: t('addOptionGroupDialog.code') }), _jsx(Input, { className: "mt-1", value:
|
|
81
|
+
} })] }), _jsxs("div", { children: [_jsx(Label, { children: t('addOptionGroupDialog.code') }), _jsx(Input, { className: "mt-1", value: form.watch('code'), onChange: (e) => {
|
|
60
82
|
setCodeEditedManually(true);
|
|
61
|
-
setField('code', e.target.value);
|
|
83
|
+
form.setField('code', e.target.value);
|
|
62
84
|
} })] }), _jsx(EntityCustomFields, { id: undefined, entityName: "productOptionGroup", hideButton: true, onChange: (cf) => {
|
|
63
|
-
setField('customFields', cf);
|
|
85
|
+
form.setField('customFields', cf);
|
|
64
86
|
}, additionalData: {}, withoutBorder: true })] }), _jsxs(DialogFooter, { className: "mt-2", children: [_jsx(Button, { onClick: () => setOpen(false), children: t('addOptionGroupDialog.cancel') }), _jsx(Button, { onClick: createGroup, children: t('addOptionGroupDialog.add') })] })] })] }));
|
|
65
87
|
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useTranslation, Input, CustomCard, CardIcons, RichTextEditor } from '@deenruv/react-ui-devkit';
|
|
3
|
-
export const BasicFieldsCard = ({ currentTranslationValue, onChange, errors }) => {
|
|
3
|
+
export const BasicFieldsCard = ({ currentTranslationValue, onChange, errors, onSlugManualEdit, }) => {
|
|
4
4
|
const { t } = useTranslation('products');
|
|
5
|
-
return (_jsx(CustomCard, { title: t('details.basicInfo'), icon: _jsx(CardIcons.basic, {}), color: "blue", children: _jsxs("div", { className: "flex flex-1 flex-col gap-y-4", children: [_jsxs("div", { className: "flex items-start gap-3", children: [_jsx(Input, { label: t('name'), placeholder: t('name'), value: currentTranslationValue?.name ?? '', onChange: (e) => onChange('name', e.target.value), errors: errors }), _jsx(Input, { value: currentTranslationValue?.slug ?? '', label: t('slug'), placeholder: t('slug'), onChange: (e) =>
|
|
5
|
+
return (_jsx(CustomCard, { title: t('details.basicInfo'), icon: _jsx(CardIcons.basic, {}), color: "blue", children: _jsxs("div", { className: "flex flex-1 flex-col gap-y-4", children: [_jsxs("div", { className: "flex items-start gap-3", children: [_jsx(Input, { label: t('name'), placeholder: t('name'), value: currentTranslationValue?.name ?? '', onChange: (e) => onChange('name', e.target.value), errors: errors }), _jsx(Input, { value: currentTranslationValue?.slug ?? '', label: t('slug'), placeholder: t('slug'), onChange: (e) => {
|
|
6
|
+
onSlugManualEdit?.();
|
|
7
|
+
onChange('slug', e.target.value);
|
|
8
|
+
} })] }), _jsx(RichTextEditor, { content: currentTranslationValue?.description ?? '', onContentChanged: (value) => onChange('description', value) })] }) }));
|
|
6
9
|
};
|
|
@@ -1,35 +1,39 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect } from 'react';
|
|
3
|
-
import { Button, Input, Card, CardHeader, CardTitle, CardContent, apiClient,
|
|
3
|
+
import { Button, Input, Card, CardHeader, CardTitle, CardContent, apiClient, useDeenruvForm, z, setInArrayBy, EntityCustomFields, useTranslation, } from '@deenruv/react-ui-devkit';
|
|
4
4
|
import { toast } from 'sonner';
|
|
5
|
+
const optionValueSchema = z.object({
|
|
6
|
+
code: z.string().default(''),
|
|
7
|
+
translations: z.array(z.object({ name: z.string(), languageCode: z.string() }).passthrough()).default([]),
|
|
8
|
+
customFields: z.record(z.string(), z.unknown()).optional().default({}),
|
|
9
|
+
});
|
|
5
10
|
export const OptionValueCard = ({ productOption, onEdited, currentTranslationLng, optionGroupId, }) => {
|
|
6
11
|
const { t } = useTranslation('products');
|
|
7
|
-
const
|
|
8
|
-
|
|
12
|
+
const form = useDeenruvForm({
|
|
13
|
+
schema: optionValueSchema,
|
|
14
|
+
defaultValues: { code: '', translations: [], customFields: {} },
|
|
15
|
+
});
|
|
16
|
+
const translations = form.watch('translations') || [];
|
|
17
|
+
const codeValue = form.watch('code');
|
|
18
|
+
const customFieldsValue = form.watch('customFields');
|
|
9
19
|
const currentTranslationValue = translations.find((v) => v.languageCode === currentTranslationLng);
|
|
10
20
|
useEffect(() => {
|
|
11
|
-
setField('code', productOption.code);
|
|
12
|
-
setField('translations', productOption.translations);
|
|
21
|
+
form.setField('code', productOption.code);
|
|
22
|
+
form.setField('translations', productOption.translations);
|
|
13
23
|
if ('customFields' in productOption) {
|
|
14
|
-
setField('customFields', productOption.customFields);
|
|
24
|
+
form.setField('customFields', productOption.customFields);
|
|
15
25
|
}
|
|
16
26
|
}, [productOption]);
|
|
17
27
|
const editOption = useCallback(() => {
|
|
18
28
|
if (productOption.id) {
|
|
19
|
-
console.log('INPUT', {
|
|
20
|
-
id: productOption.id,
|
|
21
|
-
code: state.code?.validatedValue,
|
|
22
|
-
customFields: state.customFields?.validatedValue,
|
|
23
|
-
translations: state.translations?.validatedValue,
|
|
24
|
-
});
|
|
25
29
|
return apiClient('mutation')({
|
|
26
30
|
updateProductOption: [
|
|
27
31
|
{
|
|
28
32
|
input: {
|
|
29
33
|
id: productOption.id,
|
|
30
|
-
code:
|
|
31
|
-
customFields:
|
|
32
|
-
translations:
|
|
34
|
+
code: codeValue,
|
|
35
|
+
customFields: customFieldsValue,
|
|
36
|
+
translations: translations,
|
|
33
37
|
},
|
|
34
38
|
},
|
|
35
39
|
{ id: true },
|
|
@@ -43,17 +47,15 @@ export const OptionValueCard = ({ productOption, onEdited, currentTranslationLng
|
|
|
43
47
|
toast(t('toasts.updateOptionErrorToast'));
|
|
44
48
|
});
|
|
45
49
|
}
|
|
46
|
-
}, [
|
|
50
|
+
}, [codeValue, customFieldsValue, translations, productOption, t, onEdited]);
|
|
47
51
|
return (_jsxs(Card, { className: "flex-grow basis-1/5", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "flex flex-row justify-between text-base", children: productOption.name }) }), _jsxs(CardContent, { children: [_jsx("div", { className: "flex flex-col justify-between gap-6", children: _jsxs("div", { className: "flex flex-col gap-3", children: [_jsx(Input, { label: "name", value: currentTranslationValue?.name ?? undefined, onChange: (e) => {
|
|
48
|
-
setField('translations', setInArrayBy(translations, (t) => t.languageCode
|
|
52
|
+
form.setField('translations', setInArrayBy(translations, (t) => t.languageCode === currentTranslationLng, {
|
|
49
53
|
name: e.target.value,
|
|
50
54
|
languageCode: currentTranslationLng,
|
|
51
55
|
}));
|
|
52
|
-
} }), _jsx(Input, { label: "code", value:
|
|
53
|
-
setField('code', e.target.value);
|
|
54
|
-
} }), _jsx(EntityCustomFields, { entityName: "productOption", withoutBorder: true, id: productOption.id, currentLanguage: currentTranslationLng, initialValues:
|
|
55
|
-
|
|
56
|
-
: { customFields: {} }, onChange: (cf) => {
|
|
57
|
-
setField('customFields', cf);
|
|
56
|
+
} }), _jsx(Input, { label: "code", value: codeValue ?? undefined, onChange: (e) => {
|
|
57
|
+
form.setField('code', e.target.value);
|
|
58
|
+
} }), _jsx(EntityCustomFields, { entityName: "productOption", withoutBorder: true, id: productOption.id, currentLanguage: currentTranslationLng, initialValues: customFieldsValue ? { customFields: customFieldsValue } : { customFields: {} }, onChange: (cf) => {
|
|
59
|
+
form.setField('customFields', cf);
|
|
58
60
|
}, additionalData: {} })] }) }), _jsx(Button, { size: 'sm', className: "mt-4", onClick: editOption, children: t('editOption') })] })] }));
|
|
59
61
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { EntityChannelManager, Routes, useDetailView } from '@deenruv/react-ui-devkit';
|
|
3
|
-
import { useEffect } from 'react';
|
|
3
|
+
import { useEffect, useRef } from 'react';
|
|
4
4
|
import { SettingsCard } from './SettingsCard';
|
|
5
5
|
import { CollectionsCard } from "./CollectionsCard";
|
|
6
6
|
import { FacetValuesCard } from "./FacetValuesCard";
|
|
@@ -16,15 +16,23 @@ const PRODUCT_FORM_KEYS = [
|
|
|
16
16
|
export const ProductDetailSidebar = ({ marker }) => {
|
|
17
17
|
const { form, entity } = useDetailView('products-detail-view', ...PRODUCT_FORM_KEYS);
|
|
18
18
|
const navigate = useNavigate();
|
|
19
|
-
const { base
|
|
19
|
+
const { base } = form;
|
|
20
|
+
// Guard: only set creation-mode defaults once to avoid rerender churn.
|
|
21
|
+
const creationDefaultsApplied = useRef(false);
|
|
20
22
|
useEffect(() => {
|
|
21
23
|
if (!entity) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
if (!creationDefaultsApplied.current) {
|
|
25
|
+
creationDefaultsApplied.current = true;
|
|
26
|
+
base.setField('facetValueIds', []);
|
|
27
|
+
base.setField('enabled', true);
|
|
28
|
+
}
|
|
24
29
|
return;
|
|
25
30
|
}
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
// When entity arrives, reset the guard so a subsequent unmount/remount
|
|
32
|
+
// of the sidebar (e.g. tab switch) can reinitialise if needed.
|
|
33
|
+
creationDefaultsApplied.current = false;
|
|
34
|
+
base.setField('facetValueIds', entity.facetValues.map((f) => f.id));
|
|
35
|
+
base.setField('enabled', entity.enabled);
|
|
28
36
|
}, [entity]);
|
|
29
|
-
return (_jsxs("div", { className: "flex w-full flex-col gap-4", children: [_jsx(SettingsCard, { enabledValue:
|
|
37
|
+
return (_jsxs("div", { className: "flex w-full flex-col gap-4", children: [_jsx(SettingsCard, { enabledValue: base.watch('enabled') ?? undefined, onEnabledChange: (e) => base.setField('enabled', e) }), _jsx(FacetValuesCard, { facetValuesIds: base.watch('facetValueIds') ?? undefined, onChange: (e) => base.setField('facetValueIds', e) }), !!entity?.channels?.length && (_jsx(EntityChannelManager, { entity: "product", entityChannels: entity.channels, entityId: entity.id, onRemoveSuccess: () => navigate(Routes.products.list), entitySlug: entity.slug, entityName: entity.name })), !!entity?.collections?.length && _jsx(CollectionsCard, { collections: entity.collections })] }));
|
|
30
38
|
};
|