@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.
Files changed (74) hide show
  1. package/dist/DeenruvAdminPanel.js +35 -46
  2. package/dist/access/access-context.js +10 -0
  3. package/dist/access/built-in-routes.js +300 -0
  4. package/dist/access/index.js +6 -0
  5. package/dist/access/navigation.js +33 -0
  6. package/dist/access/permission-access.js +9 -0
  7. package/dist/access/permission-routes.js +3 -0
  8. package/dist/access/types.js +1 -0
  9. package/dist/components/GlobalSearch.js +37 -62
  10. package/dist/components/Menu/ActiveAdmins.js +1 -1
  11. package/dist/components/Menu/ChannelSwitcher.js +1 -1
  12. package/dist/components/Menu/LanguagesDropdown.js +1 -1
  13. package/dist/components/Menu/Navigation.js +68 -229
  14. package/dist/components/Menu/NavigationFooter.js +7 -4
  15. package/dist/components/Menu/Notifications.js +1 -1
  16. package/dist/components/Menu/index.js +50 -16
  17. package/dist/index.css +54 -44
  18. package/dist/locales/en/admins.json +62 -0
  19. package/dist/locales/en/collections.json +7 -0
  20. package/dist/locales/en/common.json +2 -0
  21. package/dist/locales/en/orders.json +8 -0
  22. package/dist/locales/en/products.json +14 -2
  23. package/dist/locales/en/promotions.json +4 -0
  24. package/dist/locales/pl/admins.json +62 -0
  25. package/dist/locales/pl/collections.json +7 -0
  26. package/dist/locales/pl/common.json +2 -0
  27. package/dist/locales/pl/orders.json +8 -0
  28. package/dist/locales/pl/products.json +14 -2
  29. package/dist/locales/pl/promotions.json +4 -0
  30. package/dist/pages/Root.js +31 -15
  31. package/dist/pages/admins/Detail.js +1 -1
  32. package/dist/pages/admins/List.js +8 -3
  33. package/dist/pages/admins/Provision.js +173 -0
  34. package/dist/pages/admins/Provision.logic.js +53 -0
  35. package/dist/pages/admins/index.js +1 -0
  36. package/dist/pages/assets/List.js +3 -3
  37. package/dist/pages/channels/Detail.js +1 -1
  38. package/dist/pages/collections/Detail.js +21 -6
  39. package/dist/pages/collections/List.js +87 -20
  40. package/dist/pages/collections/_components/CollectionDetailView.js +15 -2
  41. package/dist/pages/collections/_components/FiltersCard.js +51 -11
  42. package/dist/pages/countries/Detail.js +1 -1
  43. package/dist/pages/customers/_components/PersonalDataCard.js +2 -1
  44. package/dist/pages/extensions/Extensions.js +3 -7
  45. package/dist/pages/facets/Detail.js +1 -1
  46. package/dist/pages/orders/Detail.js +38 -5
  47. package/dist/pages/orders/List.js +1 -16
  48. package/dist/pages/orders/_components/CancelAndRefundDialog.js +1 -2
  49. package/dist/pages/orders/_components/OrderLineCustomFields.js +1 -1
  50. package/dist/pages/orders/_components/ProductsTable.js +1 -2
  51. package/dist/pages/orders/_components/RefundPaymentCard.js +1 -2
  52. package/dist/pages/payment-methods/Detail.js +1 -1
  53. package/dist/pages/product-variants/List.js +3 -3
  54. package/dist/pages/products/Detail.js +58 -4
  55. package/dist/pages/products/_components/AssetsCard.js +15 -2
  56. package/dist/pages/products/_components/OptionsCard.js +7 -5
  57. package/dist/pages/products/_components/PriceCard.js +21 -8
  58. package/dist/pages/products/_components/ProductDetailView.js +7 -3
  59. package/dist/pages/products/_components/Variant.js +53 -26
  60. package/dist/pages/products/_components/VariantsTab.js +1 -1
  61. package/dist/pages/promotions/Detail.js +34 -3
  62. package/dist/pages/promotions/_components/EnabledCard.js +2 -2
  63. package/dist/pages/promotions/_components/PromotionDetailSidebar.js +2 -1
  64. package/dist/pages/roles/Detail.js +1 -1
  65. package/dist/pages/roles/_components/PermissionsTable.js +0 -1
  66. package/dist/pages/sellers/Detail.js +1 -1
  67. package/dist/pages/shipping-methods/Detail.js +10 -6
  68. package/dist/pages/shipping-methods/_components/CalculatorCard.js +27 -11
  69. package/dist/pages/shipping-methods/_components/CheckerCard.js +28 -12
  70. package/dist/pages/stock-locations/Detail.js +1 -1
  71. package/dist/pages/tax-rates/Detail.js +1 -1
  72. package/dist/pages/zones/Detail.js +1 -1
  73. package/dist/version.js +1 -1
  74. 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: data.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
- return id ? update({ input: { id, ...input } }) : create({ input: input });
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
- 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')] })) }), _jsx(AssetsModalInput, { setValue: (a) => onAddAsset(a?.id) })] })] }), _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') })) })] })] }) }) }));
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?.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) => {
36
- handleOptionChange(e, i);
37
- }, 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)))) }));
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
- fetchTaxRates();
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 = priceValue?.map((p) => (p.currencyCode === currencyCode ? { ...p, price: value } : p));
24
- onPriceChange(newPrices || []);
25
- }, [priceValue, onPriceChange]);
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: [priceValue?.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}%`] }) }));
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, loading, fetchEntity } = useDetailView('products-detail-view', ...PRODUCT_FORM_KEYS);
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(DetailViewMarker, { position: 'products-detail-view' }), _jsx(EntityCustomFields, { id: id, entityName: "product", hideButton: true, onChange: (customFields, translations) => {
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, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
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
- if (productId && values.sku && values.translations)
73
- return apiClient('mutation')({
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
- // price: +values.price,
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
- }, [form, productId, onActionCompleted, t]);
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
- onActionCompleted();
160
- toast(t('toasts.deleteProductVariantSuccessToast'), {
161
- description: new Date().toLocaleString(),
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?.id, entityChannels: variant?.channels ?? [], onRemoveSuccess: onActionCompleted, entityName: variant?.name, entityVariantList: {
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?.price, priceWithTax: variant?.priceWithTax, currencyCode: variant?.currencyCode },
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: data.translations,
55
- enabled: data.enabled || false,
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('products');
5
- return (_jsx(CustomCard, { title: t('details.basicInfo'), icon: _jsx(CardIcons.default, {}), color: "teal", children: _jsxs("div", { className: "flex items-center space-x-2", children: [_jsx(Switch, { id: "product-enabled", checked: enabledValue, onCheckedChange: onEnabledChange }), _jsx(Label, { htmlFor: "product-enabled", children: t('enabled') })] }) }));
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: base.watch('enabled'), onEnabledChange: (e) => base.setField('enabled', e) }) }));
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({ input: { id } });
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({ input: { id } });
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({ input: { id } });
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 hasArguments = v?.arguments.filter((a) => a.value).length;
70
+ const hasInvalidArguments = hasMissingConfigArgs(v);
67
71
  const errors = [];
68
72
  if (!hasCode)
69
73
  errors.push(t('validation.checkerCodeRequired'));
70
- if (!hasArguments)
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?.arguments.filter((a) => !a.value || a.value === 'false').length; // args have 'false' value by default
82
+ const hasInvalidArguments = hasMissingConfigArgs(v);
79
83
  const errors = [];
80
84
  if (!hasCode)
81
- errors.push(t('validation.checkerCodeRequired'));
85
+ errors.push(t('validation.calculatorCodeRequired'));
82
86
  if (hasInvalidArguments)
83
- errors.push(t('validation.checkerArgsRequired'));
87
+ errors.push(t('validation.calculatorArgsRequired'));
84
88
  return errors;
85
89
  },
86
90
  },