@deenruv/admin-dashboard 1.0.16 → 1.0.17-dev.14
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 +35 -46
- package/dist/access/access-context.js +10 -0
- package/dist/access/built-in-routes.js +300 -0
- package/dist/access/index.js +6 -0
- package/dist/access/navigation.js +33 -0
- package/dist/access/permission-access.js +9 -0
- package/dist/access/permission-routes.js +3 -0
- package/dist/access/types.js +1 -0
- package/dist/components/GlobalSearch.js +37 -62
- package/dist/components/Menu/ActiveAdmins.js +1 -1
- package/dist/components/Menu/ChannelSwitcher.js +1 -1
- package/dist/components/Menu/LanguagesDropdown.js +1 -1
- package/dist/components/Menu/Navigation.js +68 -229
- package/dist/components/Menu/NavigationFooter.js +7 -4
- package/dist/components/Menu/Notifications.js +1 -1
- package/dist/components/Menu/index.js +50 -16
- package/dist/index.css +54 -44
- package/dist/locales/en/admins.json +62 -0
- package/dist/locales/en/collections.json +7 -0
- package/dist/locales/en/common.json +2 -0
- package/dist/locales/en/orders.json +8 -0
- package/dist/locales/en/products.json +14 -2
- package/dist/locales/en/promotions.json +4 -0
- package/dist/locales/pl/admins.json +62 -0
- package/dist/locales/pl/collections.json +7 -0
- package/dist/locales/pl/common.json +2 -0
- package/dist/locales/pl/orders.json +8 -0
- package/dist/locales/pl/products.json +14 -2
- package/dist/locales/pl/promotions.json +4 -0
- package/dist/pages/Root.js +31 -15
- package/dist/pages/admins/Detail.js +1 -1
- package/dist/pages/admins/List.js +8 -3
- package/dist/pages/admins/Provision.js +173 -0
- package/dist/pages/admins/Provision.logic.js +53 -0
- package/dist/pages/admins/index.js +1 -0
- package/dist/pages/assets/List.js +3 -3
- package/dist/pages/channels/Detail.js +1 -1
- package/dist/pages/collections/Detail.js +21 -6
- package/dist/pages/collections/List.js +87 -20
- package/dist/pages/collections/_components/CollectionDetailView.js +15 -2
- package/dist/pages/collections/_components/FiltersCard.js +51 -11
- package/dist/pages/countries/Detail.js +1 -1
- package/dist/pages/customers/_components/PersonalDataCard.js +2 -1
- package/dist/pages/extensions/Extensions.js +3 -7
- package/dist/pages/facets/Detail.js +1 -1
- package/dist/pages/orders/Detail.js +38 -5
- package/dist/pages/orders/List.js +1 -16
- package/dist/pages/orders/_components/CancelAndRefundDialog.js +1 -2
- package/dist/pages/orders/_components/OrderLineCustomFields.js +1 -1
- package/dist/pages/orders/_components/ProductsTable.js +1 -2
- package/dist/pages/orders/_components/RefundPaymentCard.js +1 -2
- package/dist/pages/payment-methods/Detail.js +1 -1
- package/dist/pages/product-variants/List.js +3 -3
- package/dist/pages/products/Detail.js +58 -4
- package/dist/pages/products/_components/AssetsCard.js +15 -2
- package/dist/pages/products/_components/OptionsCard.js +7 -5
- package/dist/pages/products/_components/PriceCard.js +21 -8
- package/dist/pages/products/_components/ProductDetailView.js +7 -3
- package/dist/pages/products/_components/Variant.js +53 -26
- package/dist/pages/products/_components/VariantsTab.js +1 -1
- package/dist/pages/promotions/Detail.js +34 -3
- package/dist/pages/promotions/_components/EnabledCard.js +2 -2
- package/dist/pages/promotions/_components/PromotionDetailSidebar.js +2 -1
- package/dist/pages/roles/Detail.js +1 -1
- package/dist/pages/roles/_components/PermissionsTable.js +0 -1
- package/dist/pages/sellers/Detail.js +1 -1
- package/dist/pages/shipping-methods/Detail.js +10 -6
- package/dist/pages/shipping-methods/_components/CalculatorCard.js +27 -11
- package/dist/pages/shipping-methods/_components/CheckerCard.js +28 -12
- package/dist/pages/stock-locations/Detail.js +1 -1
- package/dist/pages/tax-rates/Detail.js +1 -1
- package/dist/pages/zones/Detail.js +1 -1
- package/dist/version.js +1 -1
- package/package.json +68 -68
|
@@ -4,7 +4,7 @@ import { VariantsTab } from "./_components/VariantsTab";
|
|
|
4
4
|
import { OptionsTab } from "./_components/OptionsTab";
|
|
5
5
|
import { ProductDetailView } from './_components/ProductDetailView';
|
|
6
6
|
import { ProductDetailSidebar } from './_components/ProductDetailSidebar';
|
|
7
|
-
import { useTranslation, createDeenruvForm, DetailView, useMutation } from '@deenruv/react-ui-devkit';
|
|
7
|
+
import { DEFAULT_CHANNEL_CODE, useTranslation, createDeenruvForm, DetailView, useMutation, useSettings, } from '@deenruv/react-ui-devkit';
|
|
8
8
|
import { $, Permission, scalars, typedGql } from '@deenruv/admin-types';
|
|
9
9
|
import { useMemo } from 'react';
|
|
10
10
|
const EditProductMutation = typedGql('mutation', { scalars })({
|
|
@@ -13,6 +13,12 @@ const EditProductMutation = typedGql('mutation', { scalars })({
|
|
|
13
13
|
const CreateProductMutation = typedGql('mutation', { scalars })({
|
|
14
14
|
createProduct: [{ input: $('input', 'CreateProductInput!') }, { id: true }],
|
|
15
15
|
});
|
|
16
|
+
const CreateProductVariantsMutation = typedGql('mutation', { scalars })({
|
|
17
|
+
createProductVariants: [{ input: $('input', '[CreateProductVariantInput!]!') }, { id: true }],
|
|
18
|
+
});
|
|
19
|
+
const AssignProductVariantsToChannelMutation = typedGql('mutation', { scalars })({
|
|
20
|
+
assignProductVariantsToChannel: [{ input: $('input', 'AssignProductVariantsToChannelInput!') }, { id: true }],
|
|
21
|
+
});
|
|
16
22
|
const DeleteProductMutation = typedGql('mutation', { scalars })({
|
|
17
23
|
deleteProduct: [{ id: $('id', 'ID!') }, { result: true }],
|
|
18
24
|
});
|
|
@@ -21,7 +27,10 @@ export const ProductsDetailPage = () => {
|
|
|
21
27
|
const { t } = useTranslation('products');
|
|
22
28
|
const [update] = useMutation(EditProductMutation);
|
|
23
29
|
const [create] = useMutation(CreateProductMutation);
|
|
30
|
+
const [createVariants] = useMutation(CreateProductVariantsMutation);
|
|
31
|
+
const [assignProductVariantsToChannel] = useMutation(AssignProductVariantsToChannelMutation);
|
|
24
32
|
const [remove] = useMutation(DeleteProductMutation);
|
|
33
|
+
const selectedChannel = useSettings((p) => p.selectedChannel);
|
|
25
34
|
const defaultTabs = useMemo(() => {
|
|
26
35
|
const tabs = [];
|
|
27
36
|
if (id) {
|
|
@@ -48,19 +57,64 @@ export const ProductsDetailPage = () => {
|
|
|
48
57
|
return [t('validation.nameSlugRequired')];
|
|
49
58
|
},
|
|
50
59
|
},
|
|
60
|
+
...{
|
|
61
|
+
initialVariantSku: {
|
|
62
|
+
validate: (v) => {
|
|
63
|
+
if (!id && (!v || typeof v !== 'string' || !v.trim())) {
|
|
64
|
+
return [t('validation.initialVariantSkuRequired')];
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
initialVariantPrice: {
|
|
69
|
+
initialValue: 0,
|
|
70
|
+
},
|
|
71
|
+
initialVariantName: {},
|
|
72
|
+
},
|
|
51
73
|
},
|
|
52
|
-
onSubmitted: (data) => {
|
|
74
|
+
onSubmitted: async (data) => {
|
|
53
75
|
if (!data.translations)
|
|
54
76
|
throw new Error('Name is required.');
|
|
77
|
+
const translations = data.translations;
|
|
55
78
|
const input = {
|
|
56
|
-
translations
|
|
79
|
+
translations,
|
|
57
80
|
assetIds: data.assetIds,
|
|
58
81
|
featuredAssetId: data.featuredAssetId,
|
|
59
82
|
facetValueIds: data.facetValueIds,
|
|
60
83
|
enabled: data.enabled,
|
|
61
84
|
...(data.customFields ? { customFields: data.customFields } : {}),
|
|
62
85
|
};
|
|
63
|
-
|
|
86
|
+
if (id)
|
|
87
|
+
return update({ input: { id, ...input } });
|
|
88
|
+
const response = await create({ input });
|
|
89
|
+
const initialVariantName = typeof data.initialVariantName === 'string' ? data.initialVariantName.trim() : '';
|
|
90
|
+
const initialVariantSku = typeof data.initialVariantSku === 'string' ? data.initialVariantSku.trim() : '';
|
|
91
|
+
const initialVariantPrice = Number(data.initialVariantPrice ?? 0);
|
|
92
|
+
const variantsResponse = await createVariants({
|
|
93
|
+
input: [
|
|
94
|
+
{
|
|
95
|
+
productId: response.createProduct.id,
|
|
96
|
+
translations: translations.map((translation) => ({
|
|
97
|
+
languageCode: translation.languageCode,
|
|
98
|
+
name: initialVariantName || translation.name || initialVariantSku,
|
|
99
|
+
})),
|
|
100
|
+
sku: initialVariantSku,
|
|
101
|
+
price: Number.isFinite(initialVariantPrice) ? initialVariantPrice : 0,
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
});
|
|
105
|
+
const createdVariantId = variantsResponse.createProductVariants?.[0]?.id;
|
|
106
|
+
if (createdVariantId &&
|
|
107
|
+
selectedChannel?.id &&
|
|
108
|
+
selectedChannel.code &&
|
|
109
|
+
selectedChannel.code !== DEFAULT_CHANNEL_CODE) {
|
|
110
|
+
await assignProductVariantsToChannel({
|
|
111
|
+
input: {
|
|
112
|
+
productVariantIds: [createdVariantId],
|
|
113
|
+
channelId: selectedChannel.id,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return response;
|
|
64
118
|
},
|
|
65
119
|
onDeleted: () => {
|
|
66
120
|
if (id)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { DropdownMenuItem, Label, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuTrigger, apiClient, cn, CustomCard, CardIcons, AssetsModalInput, useTranslation, createDialogFromComponent, } from '@deenruv/react-ui-devkit';
|
|
2
|
+
import { DropdownMenuItem, Label, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuTrigger, apiClient, cn, CustomCard, CardIcons, AssetsModalInput, AssetUploadButton, useTranslation, createDialogFromComponent, } from '@deenruv/react-ui-devkit';
|
|
3
3
|
import { assetsSelector } from "../../../graphql/base";
|
|
4
4
|
import { ImageOff } from 'lucide-react';
|
|
5
5
|
import { useCallback, useEffect, useState } from 'react';
|
|
@@ -31,5 +31,18 @@ export const AssetsCard = ({ featuredAssetId, assetsIds, onAddAsset, onFeaturedA
|
|
|
31
31
|
const newAssets = assetsIds?.filter((aId) => aId !== id);
|
|
32
32
|
onAssetsChange(newAssets || []);
|
|
33
33
|
}, [assetsIds, onAssetsChange]);
|
|
34
|
-
|
|
34
|
+
const handleAssetAdded = useCallback((id) => {
|
|
35
|
+
if (!id)
|
|
36
|
+
return;
|
|
37
|
+
if (!assetsIds?.includes(id)) {
|
|
38
|
+
onAddAsset(id);
|
|
39
|
+
}
|
|
40
|
+
if (!featuredAssetId) {
|
|
41
|
+
onFeaturedAssetChange(id);
|
|
42
|
+
}
|
|
43
|
+
}, [assetsIds, featuredAssetId, onAddAsset, onFeaturedAssetChange]);
|
|
44
|
+
return (_jsx(CustomCard, { title: t('assets'), color: "green", icon: _jsx(CardIcons.asset, {}), children: _jsx("div", { className: "flex flex-col gap-6", children: _jsxs("div", { className: "flex gap-6", children: [_jsxs("div", { children: [_jsx(Label, { children: t('details.featuredAsset') }), _jsxs("div", { className: "flex flex-col pt-3", children: [_jsx("div", { className: "mb-4 flex h-36 min-w-36 items-center justify-center border border-solid border-gray-300 p-2 shadow", children: featureAsset?.preview ? (_jsx("img", { src: featureAsset.preview, className: "h-32", alt: "Main image preview" })) : (_jsxs("div", { className: "flex size-full flex-col items-center justify-center gap-2 bg-gray-200 p-3", children: [_jsx(ImageOff, { size: 32 }), t('details.noFeaturedAsset')] })) }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(AssetsModalInput, { setValue: (a) => handleAssetAdded(a?.id) }), _jsx(AssetUploadButton, { buttonProps: { size: 'sm' }, cb: (asset) => {
|
|
45
|
+
handleAssetAdded(asset.id);
|
|
46
|
+
return {};
|
|
47
|
+
}, children: t('details.uploadAsset') })] })] })] }), _jsxs("div", { children: [_jsx(Label, { children: t('details.otherAssets') }), _jsx("div", { className: "flex gap-3 pt-3", children: assets?.length ? (assets.map((asset) => (_jsxs(DropdownMenu, { modal: false, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx("div", { className: cn('flex h-20 min-w-20 cursor-pointer items-center justify-center border border-solid border-gray-300 p-2 shadow', asset.id === featuredAssetId && 'border-2 border-blue-500'), children: asset?.preview && _jsx("img", { src: asset.preview, className: "h-16" }) }) }), _jsx(DropdownMenuContent, { className: "w-56", side: "bottom", align: "end", children: _jsxs(DropdownMenuGroup, { children: [_jsx(DropdownMenuItem, { onClick: () => createDialogFromComponent(EditAssetDialog, asset, { className: 'max-w-4xl' }), children: t('details.editAsset') }), _jsx(DropdownMenuItem, { onClick: () => onFeaturedAssetChange(asset.id), children: t('details.setAsFeatured') }), _jsx(DropdownMenuItem, { onClick: () => handleRemoveAsset(asset.id), children: t('details.removeAsset') })] }) })] }, asset.id)))) : (_jsx("p", { children: t('details.noAssets') })) })] })] }) }) }));
|
|
35
48
|
};
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useState } from 'react';
|
|
3
|
-
import { Table, TableBody, TableCell, TableRow, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, apiClient, CustomCard, CardIcons, useTranslation, } from '@deenruv/react-ui-devkit';
|
|
3
|
+
import { Table, TableBody, TableCell, TableRow, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, apiClient, CustomCard, CardIcons, useTranslation, useSettings, } from '@deenruv/react-ui-devkit';
|
|
4
4
|
import { OptionGroupSelector } from "../../../graphql/products";
|
|
5
5
|
import { toast } from 'sonner';
|
|
6
|
+
import { AddOptionGroupDialog } from "./AddOptionGroupDialog";
|
|
6
7
|
export const OptionsCard = ({ optionGroups: options, productId, onChange, optionIds, createMode, }) => {
|
|
7
8
|
const { t } = useTranslation('products');
|
|
9
|
+
const contentLng = useSettings((p) => p.translationsLanguage);
|
|
8
10
|
const [optionGroups, setOptionGroups] = useState();
|
|
9
11
|
const fetchOptionGroups = useCallback(async () => {
|
|
10
12
|
if (productId) {
|
|
@@ -23,7 +25,7 @@ export const OptionsCard = ({ optionGroups: options, productId, onChange, option
|
|
|
23
25
|
toast.error(t('toasts.fetchProductErrorToast'));
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
|
-
}, [productId]);
|
|
28
|
+
}, [productId, t]);
|
|
27
29
|
useEffect(() => {
|
|
28
30
|
fetchOptionGroups();
|
|
29
31
|
}, [fetchOptionGroups]);
|
|
@@ -32,7 +34,7 @@ export const OptionsCard = ({ optionGroups: options, productId, onChange, option
|
|
|
32
34
|
newState[groupIdx] = optionId;
|
|
33
35
|
onChange(newState);
|
|
34
36
|
}, [optionIds, onChange]);
|
|
35
|
-
return (_jsx(CustomCard, { title: t('options'), icon: _jsx(CardIcons.options, {}), color: "orange", children: !createMode ? (_jsx(Table, { children: _jsx(TableBody, { children: options?.map((o) => (_jsxs(TableRow, { children: [_jsxs(TableCell, { className: "font-semibold capitalize", children: [o.group.name, ":"] }), _jsx(TableCell, { className: "capitalize", children: o.name })] }, o.name))) }) })) : (optionGroups
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
return (_jsx(CustomCard, { title: t('options'), icon: _jsx(CardIcons.options, {}), color: "orange", upperRight: createMode ? (_jsx(AddOptionGroupDialog, { currentTranslationLng: contentLng, onSuccess: fetchOptionGroups, productId: productId })) : undefined, children: !createMode ? (_jsx(Table, { children: _jsx(TableBody, { children: options?.map((o) => (_jsxs(TableRow, { children: [_jsxs(TableCell, { className: "font-semibold capitalize", children: [o.group.name, ":"] }), _jsx(TableCell, { className: "capitalize", children: o.name })] }, o.name))) }) })) : optionGroups === undefined ? null : optionGroups.length ? (_jsx("div", { className: "flex flex-col gap-3", children: optionGroups.map((group, i) => (_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("div", { className: "w-1/3 font-semibold", children: [group.name, ":"] }), _jsx("div", { className: "w-2/3", children: _jsxs(Select, { value: optionIds?.[i] || '', onValueChange: (e) => {
|
|
38
|
+
handleOptionChange(e, i);
|
|
39
|
+
}, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: t('addVariantDialog.selectOption') }) }), _jsx(SelectContent, { children: group.options.map((o) => (_jsx(SelectItem, { value: o.id, className: "capitalize", children: o.name }, o.id))) })] }) })] }, group.name))) })) : (_jsx("p", { className: "text-sm text-muted-foreground", children: t('addVariantDialog.createOptionsHint') })) }));
|
|
38
40
|
};
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, apiClient, CustomCard, CardIcons, useTranslation, } from '@deenruv/react-ui-devkit';
|
|
3
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
4
|
-
export const PriceCard = ({ priceValue, onPriceChange, currencyCode, taxRateValue, onTaxRateChange, }) => {
|
|
3
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
4
|
+
export const PriceCard = ({ priceValue, onPriceChange, currencyCode, taxRateValue, onTaxRateChange, showDefaultPriceWhenEmpty = false, }) => {
|
|
5
5
|
const { t } = useTranslation('products');
|
|
6
6
|
const [taxCategories, setTaxCategories] = useState([]);
|
|
7
7
|
const [currentTaxCategory, setCurrentTaxCategory] = useState();
|
|
8
|
+
const defaultPrice = useMemo(() => ({
|
|
9
|
+
currencyCode,
|
|
10
|
+
price: 0,
|
|
11
|
+
}), [currencyCode]);
|
|
12
|
+
const pricesToRender = useMemo(() => (priceValue?.length ? priceValue : showDefaultPriceWhenEmpty ? [defaultPrice] : []), [defaultPrice, priceValue, showDefaultPriceWhenEmpty]);
|
|
8
13
|
useEffect(() => {
|
|
9
|
-
|
|
10
|
-
|
|
14
|
+
if (showDefaultPriceWhenEmpty && !priceValue?.length) {
|
|
15
|
+
onPriceChange([defaultPrice]);
|
|
16
|
+
}
|
|
17
|
+
}, [defaultPrice, onPriceChange, priceValue?.length, showDefaultPriceWhenEmpty]);
|
|
11
18
|
const fetchTaxRates = useCallback(async () => {
|
|
12
19
|
const response = await apiClient('query')({
|
|
13
20
|
taxCategories: [{}, { items: { id: true, name: true } }],
|
|
@@ -19,9 +26,15 @@ export const PriceCard = ({ priceValue, onPriceChange, currencyCode, taxRateValu
|
|
|
19
26
|
}));
|
|
20
27
|
setTaxCategories(categoriesWithRates);
|
|
21
28
|
}, []);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
fetchTaxRates();
|
|
31
|
+
}, [fetchTaxRates]);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
setCurrentTaxCategory(taxCategories.find((category) => category.id === taxRateValue));
|
|
34
|
+
}, [taxCategories, taxRateValue]);
|
|
22
35
|
const handlePriceChange = useCallback((currencyCode, value) => {
|
|
23
|
-
const newPrices =
|
|
24
|
-
onPriceChange(newPrices
|
|
25
|
-
}, [
|
|
26
|
-
return (_jsx(CustomCard, { title: t('details.price'), color: "rose", icon: _jsx(CardIcons.calc, {}), children: _jsxs("div", { className: "flex flex-col gap-y-4", children: [
|
|
36
|
+
const newPrices = pricesToRender.map((p) => (p.currencyCode === currencyCode ? { ...p, price: value } : p));
|
|
37
|
+
onPriceChange(newPrices);
|
|
38
|
+
}, [onPriceChange, pricesToRender]);
|
|
39
|
+
return (_jsx(CustomCard, { title: t('details.price'), color: "rose", icon: _jsx(CardIcons.calc, {}), children: _jsxs("div", { className: "flex flex-col gap-y-4", children: [pricesToRender.map((price) => (_jsx("div", { className: "flex items-center gap-x-2", children: _jsx(Input, { type: "currency", placeholder: t('price'), value: price.price, onChange: (e) => handlePriceChange(price.currencyCode, +e.target.value), step: 0.01, startAdornment: price.currencyCode }) }, price.currencyCode))), _jsxs(Select, { value: taxRateValue, onValueChange: onTaxRateChange, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Tax rate" }) }), _jsx(SelectContent, { children: taxCategories.map((tR) => (_jsx(SelectItem, { value: tR.id.toString(), children: tR.name }, tR.id))) })] }), currentTaxCategory?.value !== undefined && `${t('details.taxRateDescription')} ${currentTaxCategory?.value}%`] }) }));
|
|
27
40
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useDetailView, DetailViewMarker, useSettings, setInArrayBy, EntityCustomFields, normalizeString, } from '@deenruv/react-ui-devkit';
|
|
2
|
+
import { useDetailView, DetailViewMarker, useSettings, setInArrayBy, EntityCustomFields, normalizeString, CustomCard, CardIcons, Input, useTranslation, } from '@deenruv/react-ui-devkit';
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
4
4
|
import { BasicFieldsCard } from './BasicFieldsCard';
|
|
5
5
|
import { AssetsCard } from './AssetsCard';
|
|
@@ -13,9 +13,10 @@ export const PRODUCT_FORM_KEYS = [
|
|
|
13
13
|
'customFields',
|
|
14
14
|
];
|
|
15
15
|
export const ProductDetailView = () => {
|
|
16
|
+
const { t } = useTranslation('products');
|
|
16
17
|
const contentLng = useSettings((p) => p.translationsLanguage);
|
|
17
18
|
const selectedChannel = useSettings((p) => p.selectedChannel);
|
|
18
|
-
const { entity, id, form,
|
|
19
|
+
const { entity, id, form, fetchEntity } = useDetailView('products-detail-view', ...PRODUCT_FORM_KEYS);
|
|
19
20
|
const { base } = form;
|
|
20
21
|
// --- Slug auto-generation ---
|
|
21
22
|
// Track whether the user has manually edited the slug for the current language.
|
|
@@ -42,6 +43,7 @@ export const ProductDetailView = () => {
|
|
|
42
43
|
})();
|
|
43
44
|
}, [selectedChannel?.id, contentLng]);
|
|
44
45
|
const translations = base.watch('translations') || [];
|
|
46
|
+
const initialVariantPrice = base.watch('initialVariantPrice') ?? 0;
|
|
45
47
|
const currentTranslationValue = useMemo(() => {
|
|
46
48
|
return translations.find((v) => v.languageCode === contentLng);
|
|
47
49
|
}, [translations, contentLng]);
|
|
@@ -78,7 +80,9 @@ export const ProductDetailView = () => {
|
|
|
78
80
|
}, [base, base.setField]);
|
|
79
81
|
return (_jsx("div", { children: _jsxs("div", { className: "flex w-full flex-col gap-4", children: [_jsx(BasicFieldsCard, { currentTranslationValue: currentTranslationValue, onChange: setTranslationField, onSlugManualEdit: handleSlugManualEdit, errors: base.formState.errors?.translations?.message
|
|
80
82
|
? [base.formState.errors.translations.message]
|
|
81
|
-
: undefined }), _jsx(
|
|
83
|
+
: undefined }), !id && (_jsx(CustomCard, { title: t('initialVariant.title'), icon: _jsx(CardIcons.tag, {}), color: "rose", children: _jsxs("div", { className: "flex flex-col gap-4 pt-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: t('initialVariant.description') }), _jsxs("div", { className: "grid gap-4 md:grid-cols-3", children: [_jsx(Input, { label: t('initialVariant.sku'), value: base.watch('initialVariantSku') ?? '', onChange: (e) => base.setField('initialVariantSku', e.target.value), errors: base.formState.errors?.initialVariantSku?.message
|
|
84
|
+
? [base.formState.errors.initialVariantSku.message]
|
|
85
|
+
: undefined, required: true }), _jsx(Input, { type: "currency", label: t('initialVariant.price'), value: initialVariantPrice, onChange: (e) => base.setField('initialVariantPrice', +e.target.value), startAdornment: selectedChannel?.currencyCode, step: 0.01 }), _jsx(Input, { label: t('initialVariant.name'), placeholder: t('initialVariant.namePlaceholder'), value: base.watch('initialVariantName') ?? '', onChange: (e) => base.setField('initialVariantName', e.target.value) })] })] }) })), _jsx(DetailViewMarker, { position: 'products-detail-view' }), _jsx(EntityCustomFields, { id: id, entityName: "product", hideButton: true, onChange: (customFields, translations) => {
|
|
82
86
|
base.setField('customFields', customFields);
|
|
83
87
|
if (translations)
|
|
84
88
|
base.setField('translations', translations);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
2
|
-
import { Button, CardIcons, ConfirmationDialog, CustomCard, Input, apiClient, setInArrayBy, useDeenruvForm, z, EntityCustomFields, useTranslation, EntityChannelManager, } from '@deenruv/react-ui-devkit';
|
|
3
|
-
import { CurrencyCode } from '@deenruv/admin-types';
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Button, CardIcons, ConfirmationDialog, CustomCard, Input, apiClient, setInArrayBy, useDeenruvForm, z, EntityCustomFields, useTranslation, useSettings, EntityChannelManager, DEFAULT_CHANNEL_CODE, } from '@deenruv/react-ui-devkit';
|
|
3
|
+
import { CurrencyCode, DeletionResult } from '@deenruv/admin-types';
|
|
4
4
|
import { useCallback, useEffect } from 'react';
|
|
5
5
|
import { toast } from 'sonner';
|
|
6
6
|
import { AssetsCard } from "./AssetsCard";
|
|
@@ -10,6 +10,7 @@ import { OptionsCard } from "./OptionsCard";
|
|
|
10
10
|
import { FacetValuesCard } from "./FacetValuesCard";
|
|
11
11
|
export const Variant = ({ variant, currentTranslationLng, onActionCompleted, productId }) => {
|
|
12
12
|
const { t } = useTranslation('products');
|
|
13
|
+
const selectedChannel = useSettings((p) => p.selectedChannel);
|
|
13
14
|
const variantSchema = z.object({
|
|
14
15
|
translations: z.array(z.any()).default([]),
|
|
15
16
|
price: z.any().optional(),
|
|
@@ -50,6 +51,8 @@ export const Variant = ({ variant, currentTranslationLng, onActionCompleted, pro
|
|
|
50
51
|
const formValues = form.watch();
|
|
51
52
|
const translations = formValues.translations || [];
|
|
52
53
|
const currentTranslationValue = translations.find((v) => v.languageCode === currentTranslationLng);
|
|
54
|
+
const variantDisplayName = currentTranslationValue?.name || variant?.name || t('addVariantDialog.new');
|
|
55
|
+
const variantDisplaySku = formValues.sku || variant?.sku;
|
|
53
56
|
useEffect(() => {
|
|
54
57
|
if (!variant)
|
|
55
58
|
return;
|
|
@@ -67,18 +70,20 @@ export const Variant = ({ variant, currentTranslationLng, onActionCompleted, pro
|
|
|
67
70
|
form.setField('trackInventory', variant.trackInventory);
|
|
68
71
|
form.setField('facetValueIds', variant.facetValues.map((f) => f.id));
|
|
69
72
|
}, [variant]);
|
|
70
|
-
const createVariant = useCallback(() => {
|
|
73
|
+
const createVariant = useCallback(async () => {
|
|
71
74
|
const values = form.getValues();
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
const firstPrice = Array.isArray(values.prices) ? values.prices[0]?.price : undefined;
|
|
76
|
+
if (!productId || !values.sku || !values.translations)
|
|
77
|
+
return;
|
|
78
|
+
try {
|
|
79
|
+
const response = await apiClient('mutation')({
|
|
74
80
|
createProductVariants: [
|
|
75
81
|
{
|
|
76
82
|
input: [
|
|
77
83
|
{
|
|
78
84
|
productId,
|
|
79
85
|
translations: values.translations,
|
|
80
|
-
|
|
81
|
-
prices: values.prices,
|
|
86
|
+
price: firstPrice,
|
|
82
87
|
sku: values.sku,
|
|
83
88
|
assetIds: values.assetIds,
|
|
84
89
|
featuredAssetId: values.featuredAssetId,
|
|
@@ -100,15 +105,31 @@ export const Variant = ({ variant, currentTranslationLng, onActionCompleted, pro
|
|
|
100
105
|
id: true,
|
|
101
106
|
},
|
|
102
107
|
],
|
|
103
|
-
})
|
|
104
|
-
.then(() => {
|
|
105
|
-
toast(t('toasts.createProductVariantSuccessToast'));
|
|
106
|
-
onActionCompleted();
|
|
107
|
-
})
|
|
108
|
-
.catch(() => {
|
|
109
|
-
toast(t('toasts.createProductVariantErrorToast'));
|
|
110
108
|
});
|
|
111
|
-
|
|
109
|
+
const createdVariantId = response.createProductVariants?.[0]?.id;
|
|
110
|
+
if (createdVariantId &&
|
|
111
|
+
selectedChannel?.id &&
|
|
112
|
+
selectedChannel.code &&
|
|
113
|
+
selectedChannel.code !== DEFAULT_CHANNEL_CODE) {
|
|
114
|
+
await apiClient('mutation')({
|
|
115
|
+
assignProductVariantsToChannel: [
|
|
116
|
+
{
|
|
117
|
+
input: {
|
|
118
|
+
productVariantIds: [createdVariantId],
|
|
119
|
+
channelId: selectedChannel.id,
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{ id: true },
|
|
123
|
+
],
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
toast(t('toasts.createProductVariantSuccessToast'));
|
|
127
|
+
onActionCompleted();
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
toast(t('toasts.createProductVariantErrorToast'));
|
|
131
|
+
}
|
|
132
|
+
}, [form, productId, selectedChannel?.id, selectedChannel?.code, onActionCompleted, t]);
|
|
112
133
|
const updateVariant = useCallback(() => {
|
|
113
134
|
if (!variant)
|
|
114
135
|
return;
|
|
@@ -153,16 +174,22 @@ export const Variant = ({ variant, currentTranslationLng, onActionCompleted, pro
|
|
|
153
174
|
if (!variant)
|
|
154
175
|
return;
|
|
155
176
|
apiClient('mutation')({
|
|
156
|
-
deleteProductVariant: [{ id: variant.id }, { message: true }],
|
|
177
|
+
deleteProductVariant: [{ id: variant.id }, { message: true, result: true }],
|
|
157
178
|
})
|
|
158
|
-
.then(() => {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
179
|
+
.then(({ deleteProductVariant }) => {
|
|
180
|
+
if (deleteProductVariant.result === DeletionResult.DELETED) {
|
|
181
|
+
onActionCompleted();
|
|
182
|
+
toast(t('toasts.deleteProductVariantSuccessToast'), {
|
|
183
|
+
description: new Date().toLocaleString(),
|
|
184
|
+
});
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
toast.error(t('toasts.deleteProductVariantErrorToast'), {
|
|
188
|
+
description: deleteProductVariant.message,
|
|
162
189
|
});
|
|
163
190
|
})
|
|
164
191
|
.catch(() => toast.error(t('toasts.deleteProductVariantErrorToast')));
|
|
165
|
-
}, [variant]);
|
|
192
|
+
}, [variant, onActionCompleted, t]);
|
|
166
193
|
const setTranslationField = useCallback((field, e) => {
|
|
167
194
|
const currentValue = translations.find((t) => t.languageCode === currentTranslationLng);
|
|
168
195
|
const baseTranslation = currentValue ?? {
|
|
@@ -183,15 +210,15 @@ export const Variant = ({ variant, currentTranslationLng, onActionCompleted, pro
|
|
|
183
210
|
return;
|
|
184
211
|
form.setField('assetIds', [...newIds, id]);
|
|
185
212
|
};
|
|
186
|
-
return (_jsxs("div", { className: "mt-4 flex flex-col gap-4", children: [_jsx("div", { className: "flex gap-3 self-end", children: variant ? (_jsxs(_Fragment, { children: [_jsx(ConfirmationDialog, { onConfirm: deleteVariant, children: _jsx(Button, { variant: 'destructive', children: t('forms.removeVariant') }) }), _jsx(Button, { onClick: updateVariant, children: t('forms.updateVariant') })] })) : (_jsx(Button, { onClick: createVariant, children: t('addVariantDialog.add') })) }), _jsxs("div", { className: "flex gap-4", children: [_jsxs("div", { className: "flex w-2/3 flex-col gap-4", children: [!!variant && (_jsx(StockCard, { priceValue: formValues.price, taxRateValue: formValues.taxCategoryId, outOfStockThresholdValue: formValues.outOfStockThreshold, stockLevelsValue: formValues.stockLevels, stockOnHandValue: formValues.stockOnHand, useGlobalOutOfStockThresholdValue: formValues.useGlobalOutOfStockThreshold, onThresholdChange: (e) => form.setField('outOfStockThreshold', +e.target.value), onUseGlobalChange: (e) => form.setField('useGlobalOutOfStockThreshold', e), onTrackInventoryChange: (e) => form.setField('trackInventory', e), onStockOnHandChange: (e) => form.setField('stockOnHand', +e.target.value), onStockLocationsChange: (e) => form.setField('stockLevels', e), allStockLocations: variant?.stockLevels, stockAllocated: variant?.stockAllocated, trackInventoryValue: formValues.trackInventory })), _jsx(PriceCard, { currencyCode: variant?.currencyCode || CurrencyCode.PLN, priceValue: formValues.prices, onPriceChange: (e) => form.setField('prices', e), taxRateValue: formValues.taxCategoryId ?? undefined, onTaxRateChange: (id) => form.setField('taxCategoryId', id) }), _jsx(AssetsCard, { onAddAsset: handleAddAsset, featuredAssetId: formValues.featuredAssetId, assetsIds: formValues.assetIds, onFeaturedAssetChange: (id) => form.setField('featuredAssetId', id), onAssetsChange: (ids) => form.setField('assetIds', ids) }), _jsx(EntityCustomFields, { entityName: "productVariant", id: variant?.id, hideButton: true, onChange: (customFields, translations) => {
|
|
213
|
+
return (_jsxs("div", { className: "mt-4 flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2 rounded-lg border bg-muted/30 p-4 sm:flex-row sm:items-center sm:justify-between", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-xs font-medium tracking-wide text-muted-foreground uppercase", children: t('variants') }), _jsx("h3", { className: "truncate text-lg font-semibold", children: variantDisplayName })] }), _jsxs("div", { className: "text-sm text-muted-foreground sm:text-right", children: [_jsxs("span", { className: "font-medium text-foreground", children: [t('sku'), ":"] }), " ", variantDisplaySku || '—'] })] }), _jsx("div", { className: "flex gap-3 self-end", children: variant ? (_jsxs(_Fragment, { children: [_jsx(ConfirmationDialog, { onConfirm: deleteVariant, children: _jsx(Button, { variant: 'destructive', children: t('forms.removeVariant') }) }), _jsx(Button, { onClick: updateVariant, children: t('forms.updateVariant') })] })) : (_jsx(Button, { onClick: createVariant, children: t('addVariantDialog.add') })) }), _jsxs("div", { className: "flex gap-4", children: [_jsxs("div", { className: "flex w-2/3 flex-col gap-4", children: [!!variant && (_jsx(StockCard, { priceValue: formValues.price, taxRateValue: formValues.taxCategoryId, outOfStockThresholdValue: formValues.outOfStockThreshold, stockLevelsValue: formValues.stockLevels, stockOnHandValue: formValues.stockOnHand, useGlobalOutOfStockThresholdValue: formValues.useGlobalOutOfStockThreshold, onThresholdChange: (e) => form.setField('outOfStockThreshold', +e.target.value), onUseGlobalChange: (e) => form.setField('useGlobalOutOfStockThreshold', e), onTrackInventoryChange: (e) => form.setField('trackInventory', e), onStockOnHandChange: (e) => form.setField('stockOnHand', +e.target.value), onStockLocationsChange: (e) => form.setField('stockLevels', e), allStockLocations: variant?.stockLevels, stockAllocated: variant?.stockAllocated, trackInventoryValue: formValues.trackInventory })), _jsx(PriceCard, { currencyCode: variant?.currencyCode || CurrencyCode.PLN, priceValue: formValues.prices, onPriceChange: (e) => form.setField('prices', e), taxRateValue: formValues.taxCategoryId ?? undefined, onTaxRateChange: (id) => form.setField('taxCategoryId', id), showDefaultPriceWhenEmpty: !variant }), _jsx(AssetsCard, { onAddAsset: handleAddAsset, featuredAssetId: formValues.featuredAssetId, assetsIds: formValues.assetIds, onFeaturedAssetChange: (id) => form.setField('featuredAssetId', id), onAssetsChange: (ids) => form.setField('assetIds', ids) }), _jsx(EntityCustomFields, { entityName: "productVariant", id: variant?.id, hideButton: true, onChange: (customFields, translations) => {
|
|
187
214
|
form.setField('customFields', customFields);
|
|
188
215
|
if (translations)
|
|
189
216
|
form.setField('translations', translations);
|
|
190
217
|
}, initialValues: variant && 'customFields' in variant
|
|
191
218
|
? { customFields: variant.customFields, translations: variant.translations }
|
|
192
|
-
: { customFields: {} } })] }), _jsxs("div", { className: "flex w-1/3 flex-col gap-4", children: [_jsx(CustomCard, { title: t('name'), icon: _jsx(CardIcons.basic, {}), color: "purple", children: _jsxs("div", { className: "flex flex-col gap-y-4", children: [_jsx(Input, { label: t('sku'), placeholder: t('sku'), value: formValues.sku ?? undefined, onChange: (e) => form.setField('sku', e.target.value) }), _jsx(Input, { label: t('name'), placeholder: t('name'), value: currentTranslationValue?.name ?? undefined, onChange: (e) => setTranslationField('name', e) })] }) }), _jsx(EntityChannelManager, { entity: "productVariant", entityId: variant
|
|
219
|
+
: { customFields: {} } })] }), _jsxs("div", { className: "flex w-1/3 flex-col gap-4", children: [_jsx(CustomCard, { title: t('name'), icon: _jsx(CardIcons.basic, {}), color: "purple", children: _jsxs("div", { className: "flex flex-col gap-y-4", children: [_jsx(Input, { label: t('sku'), placeholder: t('sku'), value: formValues.sku ?? undefined, onChange: (e) => form.setField('sku', e.target.value) }), _jsx(Input, { label: t('name'), placeholder: t('name'), value: currentTranslationValue?.name ?? undefined, onChange: (e) => setTranslationField('name', e) })] }) }), variant && (_jsx(EntityChannelManager, { entity: "productVariant", entityId: variant.id, entityChannels: variant.channels ?? [], onRemoveSuccess: onActionCompleted, entityName: variant.name, entityVariantList: {
|
|
193
220
|
items: [
|
|
194
|
-
{ price: variant
|
|
221
|
+
{ price: variant.price, priceWithTax: variant.priceWithTax, currencyCode: variant.currencyCode },
|
|
195
222
|
],
|
|
196
|
-
} }), _jsx(OptionsCard, { optionGroups: variant?.options || [], productId: productId, optionIds: formValues.optionIds ?? undefined, onChange: (e) => form.setField('optionIds', e), createMode: !variant }), !!variant && (_jsx(FacetValuesCard, { facetValuesIds: formValues.facetValueIds ?? undefined, onChange: (e) => form.setField('facetValueIds', e) }))] })] })] }));
|
|
223
|
+
} })), _jsx(OptionsCard, { optionGroups: variant?.options || [], productId: productId, optionIds: formValues.optionIds ?? undefined, onChange: (e) => form.setField('optionIds', e), createMode: !variant }), !!variant && (_jsx(FacetValuesCard, { facetValuesIds: formValues.facetValueIds ?? undefined, onChange: (e) => form.setField('facetValueIds', e) }))] })] })] }));
|
|
197
224
|
};
|
|
@@ -42,5 +42,5 @@ export const VariantsTab = () => {
|
|
|
42
42
|
useEffect(() => {
|
|
43
43
|
fetchData();
|
|
44
44
|
}, [fetchData]);
|
|
45
|
-
return (_jsxs("div", { className: "flex flex-col", children: [getMarker(), _jsx("div", { className: "flex flex-col items-end gap-4", children: _jsxs(Tabs, { defaultValue: variants?.[0]?.id, className: "w-full", value: activeTab, onValueChange: handleTabChange, children: [_jsxs(TabsList, { className: "h-auto flex-wrap justify-start", children: [_jsxs(TabsTrigger, { value: NEW_VARIANT_TAB_VALUE, className: "text-blue-600", children: [_jsx(PlusCircle, { size: 16, className: "mr-2 translate-y-px" }), t('addVariantDialog.new')] }, 'new-variant'), variants?.map((v) => (_jsx(TabsTrigger, { value: v.id, children: v.name }, v.id + '-trigger')))] }), _jsx(TabsContent, { value: NEW_VARIANT_TAB_VALUE, children: id && _jsx(Variant, { currentTranslationLng: contentLanguage, onActionCompleted: fetchData, productId: id }) }, 'new-variant-content'), id && variants?.length ? (variants?.map((v) => (_jsx(TabsContent, { value: v.id, children: _jsx(Variant, { currentTranslationLng: contentLanguage, variant: v, onActionCompleted: fetchData, productId: id }) }, v.id + '-content')))) : (_jsx("div", { className: "flex w-full items-center justify-center", children: activeTab !== NEW_VARIANT_TAB_VALUE && (_jsx(EmptyState, { columnsLength: 1, title: t('variantsTab.emptyState.title'), description: t('variantsTab.emptyState.description') })) }))] }) })] }));
|
|
45
|
+
return (_jsxs("div", { className: "flex flex-col", children: [getMarker(), _jsx("div", { className: "flex flex-col items-end gap-4", children: _jsxs(Tabs, { defaultValue: variants?.[0]?.id, className: "w-full", value: activeTab, onValueChange: handleTabChange, children: [_jsxs(TabsList, { className: "h-auto flex-wrap justify-start", children: [_jsxs(TabsTrigger, { value: NEW_VARIANT_TAB_VALUE, className: "text-blue-600", children: [_jsx(PlusCircle, { size: 16, className: "mr-2 translate-y-px" }), t('addVariantDialog.new')] }, 'new-variant'), variants?.map((v) => (_jsx(TabsTrigger, { value: v.id, className: "max-w-xs", children: _jsxs("span", { className: "flex min-w-0 flex-col items-start text-left leading-tight", children: [_jsx("span", { className: "max-w-full truncate", children: v.name?.trim() || t('variantsTab.unnamedVariant') }), v.sku && (_jsx("span", { className: "max-w-full truncate text-xs font-normal text-muted-foreground", children: v.sku }))] }) }, v.id + '-trigger')))] }), _jsx(TabsContent, { value: NEW_VARIANT_TAB_VALUE, children: id && _jsx(Variant, { currentTranslationLng: contentLanguage, onActionCompleted: fetchData, productId: id }) }, 'new-variant-content'), id && variants?.length ? (variants?.map((v) => (_jsx(TabsContent, { value: v.id, children: _jsx(Variant, { currentTranslationLng: contentLanguage, variant: v, onActionCompleted: fetchData, productId: id }) }, v.id + '-content')))) : (_jsx("div", { className: "flex w-full items-center justify-center", children: activeTab !== NEW_VARIANT_TAB_VALUE && (_jsx(EmptyState, { columnsLength: 1, title: t('variantsTab.emptyState.title'), description: t('variantsTab.emptyState.description') })) }))] }) })] }));
|
|
46
46
|
};
|
|
@@ -1,9 +1,38 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useParams } from 'react-router';
|
|
3
|
-
import { useTranslation, useValidators, createDeenruvForm, DetailView, useMutation } from '@deenruv/react-ui-devkit';
|
|
3
|
+
import { useTranslation, useValidators, createDeenruvForm, DetailView, useMutation, useSettings, } from '@deenruv/react-ui-devkit';
|
|
4
4
|
import { $, Permission, scalars, typedGql } from '@deenruv/admin-types';
|
|
5
5
|
import { PromotionDetailView } from "./_components/PromotionDetailView";
|
|
6
6
|
import { PromotionDetailSidebar } from "./_components/PromotionDetailSidebar";
|
|
7
|
+
const DEFAULT_PROMOTION_LANGUAGE_CODE = 'en';
|
|
8
|
+
const ensureDefaultPromotionTranslation = (translations, defaultLanguageCode) => {
|
|
9
|
+
const sourceTranslation = translations.find((translation) => !!translation.name?.trim()) ?? translations[0];
|
|
10
|
+
if (!sourceTranslation?.name?.trim())
|
|
11
|
+
return translations;
|
|
12
|
+
let hasDefaultLanguage = false;
|
|
13
|
+
const normalizedTranslations = translations.map((translation) => {
|
|
14
|
+
if (translation.languageCode !== defaultLanguageCode)
|
|
15
|
+
return translation;
|
|
16
|
+
hasDefaultLanguage = true;
|
|
17
|
+
if (translation.name?.trim())
|
|
18
|
+
return translation;
|
|
19
|
+
return {
|
|
20
|
+
...translation,
|
|
21
|
+
name: sourceTranslation.name,
|
|
22
|
+
description: translation.description ?? sourceTranslation.description ?? '',
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
if (hasDefaultLanguage)
|
|
26
|
+
return normalizedTranslations;
|
|
27
|
+
return [
|
|
28
|
+
...normalizedTranslations,
|
|
29
|
+
{
|
|
30
|
+
languageCode: defaultLanguageCode,
|
|
31
|
+
name: sourceTranslation.name,
|
|
32
|
+
description: sourceTranslation.description ?? '',
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
};
|
|
7
36
|
const EditPromotionMutation = typedGql('mutation', { scalars })({
|
|
8
37
|
updatePromotion: [{ input: $('input', 'UpdatePromotionInput!') }, { '...on Promotion': { id: true } }],
|
|
9
38
|
});
|
|
@@ -20,6 +49,7 @@ export const PromotionsDetailPage = () => {
|
|
|
20
49
|
const [create] = useMutation(CreatePromotionMutation);
|
|
21
50
|
const [remove] = useMutation(DeletePromotionMutation);
|
|
22
51
|
const { translationsValidator, configurableOperationArrayValidator } = useValidators();
|
|
52
|
+
const defaultLanguageCode = useSettings((state) => state.selectedChannel?.defaultLanguageCode ?? DEFAULT_PROMOTION_LANGUAGE_CODE);
|
|
23
53
|
return (_jsx("div", { className: "relative flex flex-col gap-y-4", children: _jsx(DetailView, { id: id, locationId: "promotions-detail-view", main: {
|
|
24
54
|
name: 'promotion',
|
|
25
55
|
label: 'Promotion',
|
|
@@ -50,9 +80,10 @@ export const PromotionsDetailPage = () => {
|
|
|
50
80
|
throw new Error('Fill required fields.');
|
|
51
81
|
const conditions = data.conditions;
|
|
52
82
|
const actions = data.actions;
|
|
83
|
+
const translations = data.translations;
|
|
53
84
|
const input = {
|
|
54
|
-
translations:
|
|
55
|
-
enabled: data.enabled
|
|
85
|
+
translations: id ? translations : ensureDefaultPromotionTranslation(translations, defaultLanguageCode),
|
|
86
|
+
enabled: typeof data.enabled === 'boolean' ? data.enabled : true,
|
|
56
87
|
actions,
|
|
57
88
|
conditions: conditions?.map((el) => ({
|
|
58
89
|
...el,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useTranslation, Label, Switch, CustomCard, CardIcons } from '@deenruv/react-ui-devkit';
|
|
3
3
|
export const EnabledCard = ({ onEnabledChange, enabledValue }) => {
|
|
4
|
-
const { t } = useTranslation('
|
|
5
|
-
return (_jsx(CustomCard, { title: t('
|
|
4
|
+
const { t } = useTranslation('promotions');
|
|
5
|
+
return (_jsx(CustomCard, { title: t('enabled.header'), icon: _jsx(CardIcons.default, {}), color: "teal", children: _jsxs("div", { className: "flex items-center space-x-2", children: [_jsx(Switch, { id: "promotion-enabled", checked: enabledValue ?? false, onCheckedChange: onEnabledChange }), _jsx(Label, { htmlFor: "promotion-enabled", children: t('enabled.label') })] }) }));
|
|
6
6
|
};
|
|
@@ -6,6 +6,7 @@ import { useEffect } from 'react';
|
|
|
6
6
|
export const PromotionDetailSidebar = () => {
|
|
7
7
|
const { id, form } = useDetailView('promotions-detail-view', 'CreatePromotionInput', 'enabled');
|
|
8
8
|
const { base } = form;
|
|
9
|
+
const enabledValue = base.watch('enabled');
|
|
9
10
|
// Always call useQuery unconditionally to preserve hook order.
|
|
10
11
|
// Pass a placeholder id when none exists; the result will simply be ignored.
|
|
11
12
|
const { data } = useQuery(PromotionQuery, { initialVariables: { id: id ?? '' } });
|
|
@@ -14,5 +15,5 @@ export const PromotionDetailSidebar = () => {
|
|
|
14
15
|
base.setField('enabled', data.promotion.enabled);
|
|
15
16
|
}
|
|
16
17
|
}, [data, id]);
|
|
17
|
-
return (_jsx("div", { className: "flex w-full flex-col gap-4", children: _jsx(EnabledCard, { enabledValue:
|
|
18
|
+
return (_jsx("div", { className: "flex w-full flex-col gap-4", children: _jsx(EnabledCard, { enabledValue: id ? enabledValue : (enabledValue ?? true), onEnabledChange: (e) => base.setField('enabled', e) }) }));
|
|
18
19
|
};
|
|
@@ -42,7 +42,7 @@ export const RolesDetailPage = () => {
|
|
|
42
42
|
if (!id) {
|
|
43
43
|
throw new Error('Could not find the id.');
|
|
44
44
|
}
|
|
45
|
-
return remove({
|
|
45
|
+
return remove({ id });
|
|
46
46
|
}, [remove, id]);
|
|
47
47
|
return (_jsx("div", { className: "relative flex flex-col gap-y-4", children: _jsx(DetailView, { id: id, locationId: "roles-detail-view", main: {
|
|
48
48
|
name: 'role',
|
|
@@ -2,7 +2,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useCallback, useEffect, useState } from 'react';
|
|
3
3
|
import { useTranslation, cn, Table, TableBody, TableCell, TableRow, useServer } from '@deenruv/react-ui-devkit';
|
|
4
4
|
import { CircleCheckBig } from 'lucide-react';
|
|
5
|
-
import permissionsJson from "../../../locales/en/permissions.json";
|
|
6
5
|
export const PermissionsTable = ({ currentPermissions, onPermissionsChange }) => {
|
|
7
6
|
const { t } = useTranslation('permissions');
|
|
8
7
|
const [groupedPermissions, setGroupedPermissions] = useState();
|
|
@@ -31,7 +31,7 @@ export const SellersDetailPage = () => {
|
|
|
31
31
|
if (!id) {
|
|
32
32
|
throw new Error('Could not find the id.');
|
|
33
33
|
}
|
|
34
|
-
return remove({
|
|
34
|
+
return remove({ id });
|
|
35
35
|
}, [remove, id]);
|
|
36
36
|
return (_jsx("div", { className: "relative flex flex-col gap-y-4", children: _jsx(DetailView, { id: id, locationId: "sellers-detail-view", main: {
|
|
37
37
|
name: 'seller',
|
|
@@ -3,6 +3,10 @@ import { useCallback } from 'react';
|
|
|
3
3
|
import { useParams } from 'react-router';
|
|
4
4
|
import { useTranslation, DetailView, createDeenruvForm, getMutation, useMutation } from '@deenruv/react-ui-devkit';
|
|
5
5
|
import { ShippingMethodDetailView } from "./_components/ShippingMethodDetailView.js";
|
|
6
|
+
const isMissingConfigArgValue = (value) => value == null || value.trim() === '';
|
|
7
|
+
const hasMissingConfigArgs = (operation) => {
|
|
8
|
+
return !!operation?.arguments?.some((argument) => isMissingConfigArgValue(argument.value));
|
|
9
|
+
};
|
|
6
10
|
const CreateShippingMethodMutation = getMutation('createShippingMethod');
|
|
7
11
|
const EditShippingMethodMutation = getMutation('updateShippingMethod');
|
|
8
12
|
const DeleteShippingMethodMutation = getMutation('deleteShippingMethod');
|
|
@@ -44,7 +48,7 @@ export const ShippingMethodsDetailPage = () => {
|
|
|
44
48
|
if (!id) {
|
|
45
49
|
throw new Error('Could not find the id.');
|
|
46
50
|
}
|
|
47
|
-
return remove({
|
|
51
|
+
return remove({ id });
|
|
48
52
|
}, [remove, id]);
|
|
49
53
|
return (_jsx("div", { className: "relative flex flex-col gap-y-4", children: _jsx(DetailView, { id: id, locationId: "shippingMethods-detail-view", main: {
|
|
50
54
|
name: 'shippingMethod',
|
|
@@ -63,11 +67,11 @@ export const ShippingMethodsDetailPage = () => {
|
|
|
63
67
|
checker: {
|
|
64
68
|
validate: (v) => {
|
|
65
69
|
const hasCode = !!v?.code;
|
|
66
|
-
const
|
|
70
|
+
const hasInvalidArguments = hasMissingConfigArgs(v);
|
|
67
71
|
const errors = [];
|
|
68
72
|
if (!hasCode)
|
|
69
73
|
errors.push(t('validation.checkerCodeRequired'));
|
|
70
|
-
if (
|
|
74
|
+
if (hasInvalidArguments)
|
|
71
75
|
errors.push(t('validation.checkerArgsRequired'));
|
|
72
76
|
return errors;
|
|
73
77
|
},
|
|
@@ -75,12 +79,12 @@ export const ShippingMethodsDetailPage = () => {
|
|
|
75
79
|
calculator: {
|
|
76
80
|
validate: (v) => {
|
|
77
81
|
const hasCode = !!v?.code;
|
|
78
|
-
const hasInvalidArguments = v
|
|
82
|
+
const hasInvalidArguments = hasMissingConfigArgs(v);
|
|
79
83
|
const errors = [];
|
|
80
84
|
if (!hasCode)
|
|
81
|
-
errors.push(t('validation.
|
|
85
|
+
errors.push(t('validation.calculatorCodeRequired'));
|
|
82
86
|
if (hasInvalidArguments)
|
|
83
|
-
errors.push(t('validation.
|
|
87
|
+
errors.push(t('validation.calculatorArgsRequired'));
|
|
84
88
|
return errors;
|
|
85
89
|
},
|
|
86
90
|
},
|