@deenruv/admin-dashboard 1.0.15 → 1.0.17-dev.4

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 (48) hide show
  1. package/dist/DeenruvAdminPanel.js +4 -4
  2. package/dist/components/GlobalSearch.js +5 -5
  3. package/dist/components/Menu/ActiveAdmins.js +1 -1
  4. package/dist/components/Menu/ChannelSwitcher.js +1 -1
  5. package/dist/components/Menu/LanguagesDropdown.js +1 -1
  6. package/dist/components/Menu/Navigation.js +4 -4
  7. package/dist/components/Menu/NavigationFooter.js +2 -2
  8. package/dist/components/Menu/Notifications.js +1 -1
  9. package/dist/components/Menu/index.js +12 -14
  10. package/dist/index.css +54 -44
  11. package/dist/locales/en/collections.json +7 -0
  12. package/dist/locales/en/common.json +1 -0
  13. package/dist/locales/en/orders.json +8 -0
  14. package/dist/locales/en/products.json +14 -2
  15. package/dist/locales/en/promotions.json +4 -0
  16. package/dist/locales/pl/collections.json +7 -0
  17. package/dist/locales/pl/common.json +1 -0
  18. package/dist/locales/pl/orders.json +8 -0
  19. package/dist/locales/pl/products.json +14 -2
  20. package/dist/locales/pl/promotions.json +4 -0
  21. package/dist/pages/Root.js +1 -1
  22. package/dist/pages/collections/Detail.js +20 -5
  23. package/dist/pages/collections/_components/CollectionDetailView.js +15 -2
  24. package/dist/pages/collections/_components/FiltersCard.js +51 -11
  25. package/dist/pages/customers/_components/PersonalDataCard.js +2 -1
  26. package/dist/pages/extensions/Extensions.js +3 -7
  27. package/dist/pages/orders/Detail.js +38 -5
  28. package/dist/pages/orders/List.js +1 -16
  29. package/dist/pages/orders/_components/CancelAndRefundDialog.js +1 -2
  30. package/dist/pages/orders/_components/OrderLineCustomFields.js +1 -1
  31. package/dist/pages/orders/_components/ProductsTable.js +1 -2
  32. package/dist/pages/orders/_components/RefundPaymentCard.js +1 -2
  33. package/dist/pages/products/Detail.js +58 -4
  34. package/dist/pages/products/_components/AssetsCard.js +15 -2
  35. package/dist/pages/products/_components/OptionsCard.js +7 -5
  36. package/dist/pages/products/_components/PriceCard.js +21 -8
  37. package/dist/pages/products/_components/ProductDetailView.js +7 -3
  38. package/dist/pages/products/_components/Variant.js +40 -19
  39. package/dist/pages/products/_components/VariantsTab.js +1 -1
  40. package/dist/pages/promotions/Detail.js +34 -3
  41. package/dist/pages/promotions/_components/EnabledCard.js +2 -2
  42. package/dist/pages/promotions/_components/PromotionDetailSidebar.js +2 -1
  43. package/dist/pages/roles/_components/PermissionsTable.js +0 -1
  44. package/dist/pages/shipping-methods/Detail.js +9 -5
  45. package/dist/pages/shipping-methods/_components/CalculatorCard.js +27 -11
  46. package/dist/pages/shipping-methods/_components/CheckerCard.js +28 -12
  47. package/dist/version.js +1 -1
  48. package/package.json +68 -68
@@ -42,6 +42,14 @@
42
42
  "removeVariant": "Usuń wariant",
43
43
  "removeVariantFromChannel": "Usuń wariant z kanału"
44
44
  },
45
+ "initialVariant": {
46
+ "title": "Początkowy wariant",
47
+ "description": "Utwórz jeden domyślny wariant razem z tym produktem. Jeśli nazwa wariantu jest pusta, zostanie użyta nazwa produktu.",
48
+ "sku": "SKU",
49
+ "price": "Cena",
50
+ "name": "Nazwa wariantu",
51
+ "namePlaceholder": "Użyj nazwy produktu"
52
+ },
45
53
  "optionsTab": {
46
54
  "placeholder": "Wpisz, aby dodać nową wartość opcji",
47
55
  "groupName": "Nazwa grupy",
@@ -53,6 +61,7 @@
53
61
  "noOptionValues": "Dodaj opcje powyżej, aby znaleźć je tutaj"
54
62
  },
55
63
  "variantsTab": {
64
+ "unnamedVariant": "Nienazwany wariant",
56
65
  "emptyState": {
57
66
  "title": "Nic tutaj nie ma",
58
67
  "description": "Nie ma jeszcze żadnych wariantów. Użyj przycisku powyżej, aby je dodać, pod warunkiem, że dodałeś już jakieś opcje. Jeśli nie, utwórz je najpierw w zakładce Opcje."
@@ -128,6 +137,7 @@
128
137
  "noAssets": "Nie ma jeszcze żadnych aktywów",
129
138
  "setAsFeatured": "Ustaw jako polecane",
130
139
  "editAsset": "Edytuj zdjęcie",
140
+ "uploadAsset": "Prześlij obraz",
131
141
  "removeAsset": "Usuń zasób",
132
142
  "usedForColorsDesc": "Użyj tego atrybutu dla facetów, którzy są kolorami",
133
143
  "colorsCollectionDesc": "Użyj tego dla ścieżek produktu w KOLORZE (BLAT/CORPUS)",
@@ -186,9 +196,11 @@
186
196
  "new": "Nowy wariant",
187
197
  "selectOption": "Wybierz opcję",
188
198
  "createBasic": "Dane podstawowe",
189
- "createOptions": "Opcje"
199
+ "createOptions": "Opcje",
200
+ "createOptionsHint": "Dodaj grupę opcji w tym panelu, aby rozpocząć wybieranie opcji wariantu. Wartości opcji możesz później dopracować w zakładce Opcje."
190
201
  },
191
202
  "validation": {
192
- "nameSlugRequired": "Pola nazwy i slug są wymagane"
203
+ "nameSlugRequired": "Pola nazwy i slug są wymagane",
204
+ "initialVariantSkuRequired": "SKU wariantu jest wymagane"
193
205
  }
194
206
  }
@@ -13,6 +13,10 @@
13
13
  "usageLimit": "Limit użycia",
14
14
  "perCustomerUsageLimit": "Limit użycia na klienta"
15
15
  },
16
+ "enabled": {
17
+ "header": "Status",
18
+ "label": "Promocja aktywna"
19
+ },
16
20
  "conditions": {
17
21
  "header": "Warunki",
18
22
  "add": "Dodaj nowy warunek",
@@ -248,5 +248,5 @@ export const Root = ({ allPaths }) => {
248
248
  await init();
249
249
  });
250
250
  }, []);
251
- return (_jsx(_Fragment, { children: _jsxs("div", { className: "flex max-h-screen w-full max-w-full overflow-hidden bg-background text-foreground", children: [_jsx(Menu, { children: loaded ? _jsx(Outlet, {}) : _jsx(ContentAreaSkeleton, {}) }), loaded && (_jsxs(_Fragment, { children: [_jsx(GlobalSearch, {}), _jsx(CanLeaveRouteDialog, {}), isLocalhost ? _jsx(DeenruvDeveloperIndicator, {}) : null] }))] }) }));
251
+ return (_jsx(_Fragment, { children: _jsxs("div", { className: "flex max-h-screen w-full max-w-full overflow-hidden bg-background text-foreground antialiased", children: [_jsx(Menu, { children: loaded ? _jsx(Outlet, {}) : _jsx(ContentAreaSkeleton, {}) }), loaded && (_jsxs(_Fragment, { children: [_jsx(GlobalSearch, {}), _jsx(CanLeaveRouteDialog, {}), isLocalhost ? _jsx(DeenruvDeveloperIndicator, {}) : null] }))] }) }));
252
252
  };
@@ -1,18 +1,21 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useCallback } from 'react';
3
3
  import { useParams } from 'react-router';
4
- import { useValidators, DetailView, createDeenruvForm, getMutation, useMutation } from '@deenruv/react-ui-devkit';
4
+ import { useValidators, DetailView, createDeenruvForm, getMutation, useMutation, useSettings, } from '@deenruv/react-ui-devkit';
5
5
  import { CollectionsDetailView } from "./_components/CollectionDetailView.js";
6
6
  const CreateCollectionMutation = getMutation('createCollection');
7
7
  const EditCollectionMutation = getMutation('updateCollection');
8
8
  const DeleteCollectionMutation = getMutation('deleteCollection');
9
+ const AssignCollectionsToChannelMutation = getMutation('assignCollectionsToChannel');
9
10
  export const CollectionsDetailPage = () => {
10
11
  const { id } = useParams();
11
12
  const [update] = useMutation(EditCollectionMutation);
12
13
  const [create] = useMutation(CreateCollectionMutation);
13
14
  const [remove] = useMutation(DeleteCollectionMutation);
15
+ const [assignCollectionsToChannel] = useMutation(AssignCollectionsToChannelMutation);
16
+ const selectedChannel = useSettings((p) => p.selectedChannel);
14
17
  const { configurableOperationArrayValidator, translationsValidator } = useValidators();
15
- const onSubmitHandler = useCallback((data) => {
18
+ const onSubmitHandler = useCallback(async (data) => {
16
19
  const translations = data.translations;
17
20
  const inputData = {
18
21
  assetIds: data.assetIds,
@@ -29,9 +32,16 @@ export const CollectionsDetailPage = () => {
29
32
  };
30
33
  if (id)
31
34
  return update({ input: { id, ...inputData } });
32
- else
33
- return create({ input: inputData });
34
- }, [id, update, create]);
35
+ const response = await create({ input: inputData });
36
+ const collectionId = response.createCollection.id;
37
+ const additionalChannelIds = Array.isArray(data.additionalChannelIds)
38
+ ? data.additionalChannelIds.filter((channelId) => typeof channelId === 'string' && channelId !== selectedChannel?.id)
39
+ : [];
40
+ for (const channelId of additionalChannelIds) {
41
+ await assignCollectionsToChannel({ input: { collectionIds: [collectionId], channelId } });
42
+ }
43
+ return response;
44
+ }, [id, update, create, assignCollectionsToChannel, selectedChannel?.id]);
35
45
  const onDeleteHandler = useCallback(() => {
36
46
  if (!id) {
37
47
  throw new Error('Could not find the id.');
@@ -52,6 +62,11 @@ export const CollectionsDetailPage = () => {
52
62
  inheritFilters: {
53
63
  initialValue: true,
54
64
  },
65
+ ...{
66
+ additionalChannelIds: {
67
+ initialValue: [],
68
+ },
69
+ },
55
70
  translations: translationsValidator,
56
71
  filters: configurableOperationArrayValidator(),
57
72
  },
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useRef } from 'react';
3
- import { EntityCustomFields, Input, Label, Switch, CustomCard, CardIcons, useDetailView, useSettings, RichTextEditor, useTranslation, setInArrayBy, EntityChannelManager, Routes, normalizeString, } from '@deenruv/react-ui-devkit';
3
+ import { EntityCustomFields, Input, Label, Switch, Checkbox, CustomCard, CardIcons, useDetailView, useSettings, useServer, RichTextEditor, useTranslation, setInArrayBy, EntityChannelManager, Routes, normalizeString, } from '@deenruv/react-ui-devkit';
4
4
  import { FiltersCard } from "./FiltersCard";
5
5
  import { ContentsCard } from "./ContentsCard";
6
6
  import { AssetsCard } from "../../products/_components/AssetsCard.js";
@@ -9,6 +9,7 @@ export const CollectionsDetailView = () => {
9
9
  const { t } = useTranslation('collections');
10
10
  const contentLng = useSettings((p) => p.translationsLanguage);
11
11
  const selectedChannel = useSettings((p) => p.selectedChannel);
12
+ const channels = useServer((p) => p.channels);
12
13
  const navigate = useNavigate();
13
14
  const { form, fetchEntity, entity, id } = useDetailView('collections-detail-view', 'CreateCollectionInput', 'translations', 'assetIds', 'featuredAssetId', 'isPrivate', 'inheritFilters', 'filters', 'customFields');
14
15
  const { base } = form;
@@ -40,7 +41,15 @@ export const CollectionsDetailView = () => {
40
41
  })();
41
42
  }, [contentLng, selectedChannel?.id]);
42
43
  const translations = base.watch('translations') || [];
44
+ const additionalChannelIds = base.watch('additionalChannelIds') ?? [];
43
45
  const currentTranslationValue = translations.find((v) => v.languageCode === contentLng);
46
+ const toggleAdditionalChannel = useCallback((channelId, checked) => {
47
+ if (channelId === selectedChannel?.id)
48
+ return;
49
+ const currentIds = base.watch('additionalChannelIds') ?? [];
50
+ const nextIds = checked ? [...new Set([...currentIds, channelId])] : currentIds.filter((id) => id !== channelId);
51
+ base.setField('additionalChannelIds', nextIds);
52
+ }, [base, selectedChannel?.id]);
44
53
  const handleAddAsset = useCallback((newId) => {
45
54
  if (!newId)
46
55
  return;
@@ -85,7 +94,11 @@ export const CollectionsDetailView = () => {
85
94
  : undefined, required: true }) }), _jsx("div", { className: "flex basis-full md:basis-1/3", children: _jsx(Input, { label: t('details.basic.slug'), value: currentTranslationValue?.slug ?? '', onChange: (e) => {
86
95
  handleSlugManualEdit();
87
96
  setTranslationField('slug', e.target.value);
88
- }, required: true }) }), _jsxs("div", { className: "mt-7 flex basis-full items-center gap-3 md:basis-1/3", children: [_jsx(Switch, { checked: base.watch('isPrivate') ?? false, onCheckedChange: (e) => base.setField('isPrivate', e) }), _jsx(Label, { children: t('details.basic.isPrivate') })] })] }), _jsxs("div", { className: "flex basis-full flex-col", children: [_jsx(Label, { className: "mb-2", children: t('details.basic.description') }), _jsx(RichTextEditor, { content: currentTranslationValue?.description ?? '', onContentChanged: (e) => setTranslationField('description', e) })] })] }) }), _jsx(AssetsCard, { onAddAsset: handleAddAsset, featuredAssetId: base.watch('featuredAssetId') ?? undefined, assetsIds: base.watch('assetIds') ?? undefined, onFeaturedAssetChange: (id) => base.setField('featuredAssetId', id), onAssetsChange: (ids) => base.setField('assetIds', ids) }), _jsx(EntityChannelManager, { entity: "collection", entityId: id, entityChannels: [{ id: selectedChannel?.id, code: selectedChannel?.code }], onRemoveSuccess: () => navigate(Routes.collections.list) }), _jsx(FiltersCard, { currentFiltersValue: base.watch('filters') ?? undefined, onFiltersValueChange: (filters) => base.setField('filters', filters ?? []), inheritValue: base.watch('inheritFilters') ?? false, onInheritChange: (e) => base.setField('inheritFilters', e), errors: base.formState.errors?.filters?.message ? [base.formState.errors.filters.message] : undefined }), _jsx(EntityCustomFields, { entityName: "collection", id: id, hideButton: true, onChange: (customFields, translations) => {
97
+ }, required: true }) }), _jsxs("div", { className: "mt-7 flex basis-full items-center gap-3 md:basis-1/3", children: [_jsx(Switch, { checked: base.watch('isPrivate') ?? false, onCheckedChange: (e) => base.setField('isPrivate', e) }), _jsx(Label, { children: t('details.basic.isPrivate') })] })] }), _jsxs("div", { className: "flex basis-full flex-col", children: [_jsx(Label, { className: "mb-2", children: t('details.basic.description') }), _jsx(RichTextEditor, { content: currentTranslationValue?.description ?? '', onContentChanged: (e) => setTranslationField('description', e) })] })] }) }), _jsx(AssetsCard, { onAddAsset: handleAddAsset, featuredAssetId: base.watch('featuredAssetId') ?? undefined, assetsIds: base.watch('assetIds') ?? undefined, onFeaturedAssetChange: (id) => base.setField('featuredAssetId', id), onAssetsChange: (ids) => base.setField('assetIds', ids) }), id ? (_jsx(EntityChannelManager, { entity: "collection", entityId: id, entityChannels: [{ id: selectedChannel?.id, code: selectedChannel?.code }], onRemoveSuccess: () => navigate(Routes.collections.list) })) : (_jsx(CustomCard, { title: t('details.channels.title'), icon: _jsx(CardIcons.shipping, {}), color: "green", children: _jsxs("div", { className: "flex flex-col gap-4 pt-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: t('details.channels.description') }), _jsx("div", { className: "grid gap-3 md:grid-cols-2", children: channels.map((channel) => {
98
+ const isCurrentChannel = channel.id === selectedChannel?.id;
99
+ const checked = isCurrentChannel || additionalChannelIds.includes(channel.id);
100
+ return (_jsxs("label", { className: "flex items-center gap-3 rounded-md border p-3 text-sm transition-colors hover:bg-muted/50", children: [_jsx(Checkbox, { checked: checked, disabled: isCurrentChannel, onCheckedChange: (value) => toggleAdditionalChannel(channel.id, !!value) }), _jsx("span", { className: "font-medium", children: channel.code }), isCurrentChannel && (_jsx("span", { className: "ml-auto rounded-full bg-muted px-2 py-1 text-xs text-muted-foreground", children: t('details.channels.currentChannel') }))] }, channel.id));
101
+ }) }), !channels.length && _jsx("p", { className: "text-sm text-muted-foreground", children: t('details.channels.noChannels') })] }) })), _jsx(FiltersCard, { currentFiltersValue: base.watch('filters') ?? undefined, onFiltersValueChange: (filters) => base.setField('filters', filters ?? []), inheritValue: base.watch('inheritFilters') ?? false, onInheritChange: (e) => base.setField('inheritFilters', e), errors: base.formState.errors?.filters?.message ? [base.formState.errors.filters.message] : undefined }), _jsx(EntityCustomFields, { entityName: "collection", id: id, hideButton: true, onChange: (customFields, translations) => {
89
102
  base.setField('customFields', customFields);
90
103
  if (translations)
91
104
  base.setField('translations', translations);
@@ -5,6 +5,44 @@ import { X } from 'lucide-react';
5
5
  import { FacetsSelector } from "./FacetsSelector";
6
6
  import { CombinationMode } from "./CombinationMode";
7
7
  import { VariantsSelector } from "./VariantsSelector";
8
+ const getArgumentComponent = (argument) => argument?.ui?.component;
9
+ const getArgumentOptions = (argument) => argument?.ui?.options ?? [];
10
+ const stringifyDefaultValue = (value) => {
11
+ if (Array.isArray(value) || (typeof value === 'object' && value !== null)) {
12
+ return JSON.stringify(value);
13
+ }
14
+ if (typeof value === 'boolean' || typeof value === 'number' || typeof value === 'string') {
15
+ return String(value);
16
+ }
17
+ return '';
18
+ };
19
+ const getDefaultArgumentValue = (argument) => {
20
+ if (argument.defaultValue !== undefined && argument.defaultValue !== null) {
21
+ return stringifyDefaultValue(argument.defaultValue);
22
+ }
23
+ const component = getArgumentComponent(argument);
24
+ if (argument.list || component === 'facet-value-form-input' || component === 'product-multi-form-input') {
25
+ return '[]';
26
+ }
27
+ if (component === 'select-form-input') {
28
+ return getArgumentOptions(argument)[0]?.value ?? '';
29
+ }
30
+ if (argument.type === 'boolean') {
31
+ return 'false';
32
+ }
33
+ return '';
34
+ };
35
+ const parseStringArrayValue = (value) => {
36
+ if (!value)
37
+ return [];
38
+ try {
39
+ const parsed = JSON.parse(value);
40
+ return Array.isArray(parsed) ? parsed.filter((item) => typeof item === 'string') : [];
41
+ }
42
+ catch {
43
+ return [];
44
+ }
45
+ };
8
46
  export const FiltersCard = ({ currentFiltersValue, onFiltersValueChange, inheritValue, onInheritChange, errors, }) => {
9
47
  const { t } = useTranslation('collections');
10
48
  const codesTranslations = t('details.filters.labels.codes', { returnObjects: true });
@@ -27,14 +65,14 @@ export const FiltersCard = ({ currentFiltersValue, onFiltersValueChange, inherit
27
65
  }, [fetchOptions]);
28
66
  const handleFiltersValueChange = useCallback((index, code, args) => {
29
67
  const correspondingFilter = filters.find((h) => h?.code === code);
30
- if (correspondingFilter && currentFiltersValue) {
31
- const newFiltersValue = [...currentFiltersValue];
68
+ if (correspondingFilter) {
69
+ const newFiltersValue = [...(currentFiltersValue || [])];
32
70
  newFiltersValue[index] = {
33
71
  code: correspondingFilter.code,
34
72
  arguments: args ||
35
73
  correspondingFilter.args.map((a) => ({
36
74
  name: a.name,
37
- value: 'false',
75
+ value: getDefaultArgumentValue(a),
38
76
  })),
39
77
  };
40
78
  onFiltersValueChange(newFiltersValue);
@@ -49,38 +87,40 @@ export const FiltersCard = ({ currentFiltersValue, onFiltersValueChange, inherit
49
87
  }, [onFiltersValueChange, currentFiltersValue]);
50
88
  const handleAddFilter = useCallback(() => {
51
89
  const defaultFilter = filters[0];
90
+ if (!defaultFilter)
91
+ return;
52
92
  const newFiltersValue = [
53
93
  ...(currentFiltersValue || []),
54
94
  {
55
95
  code: defaultFilter.code,
56
96
  arguments: defaultFilter.args.map((a) => ({
57
97
  name: a.name,
58
- value: 'false',
98
+ value: getDefaultArgumentValue(a),
59
99
  })),
60
100
  },
61
101
  ];
62
102
  onFiltersValueChange(newFiltersValue);
63
- }, [onFiltersValueChange, currentFiltersValue]);
64
- return (_jsx(CustomCard, { title: t('details.filters.title'), upperRight: _jsx(ErrorMessage, { errors: errors }), color: "orange", icon: _jsx(CardIcons.filter, {}), children: _jsx("div", { className: "flex- flex flex-wrap gap-4 xl:flex-nowrap", children: _jsxs("div", { className: "flex basis-full flex-col gap-4", children: [_jsxs("div", { className: "mb-2 flex basis-full", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Switch, { checked: inheritValue, onCheckedChange: onInheritChange }), _jsx(Label, { children: t('details.filters.inherit') })] }), _jsx(Button, { className: "ml-auto", onClick: handleAddFilter, children: t('details.filters.addCondition') })] }), currentFiltersValue?.map((filter, index) => {
103
+ }, [filters, onFiltersValueChange, currentFiltersValue]);
104
+ return (_jsx(CustomCard, { title: t('details.filters.title'), upperRight: _jsx(ErrorMessage, { errors: errors }), color: "orange", icon: _jsx(CardIcons.filter, {}), children: _jsx("div", { className: "flex- flex flex-wrap gap-4 xl:flex-nowrap", children: _jsxs("div", { className: "flex basis-full flex-col gap-4", children: [_jsxs("div", { className: "mb-2 flex basis-full", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Switch, { checked: inheritValue, onCheckedChange: onInheritChange }), _jsx(Label, { children: t('details.filters.inherit') })] }), _jsx(Button, { className: "ml-auto", onClick: handleAddFilter, disabled: filters.length === 0, title: filters.length === 0 ? t('details.filters.noAvailableConditions') : undefined, children: t('details.filters.addCondition') })] }), currentFiltersValue?.map((filter, index) => {
65
105
  return (_jsxs("div", { className: "flex flex-col gap-4", children: [_jsx(Separator, {}), _jsxs("div", { className: "flex w-1/2 items-end gap-1", children: [_jsx(SimpleSelect, { label: t('details.filters.condition'), value: filter ? filter.code : '', onValueChange: (e) => handleFiltersValueChange(index, e), options: allFiltersOptions }), filter?.code && (_jsx(Button, { variant: 'secondary', className: "p-2", onClick: (e) => {
66
106
  e.preventDefault();
67
107
  handleRemoveFilter(filter.code);
68
108
  }, children: _jsx(X, { size: 20 }) }))] }), _jsx("div", { className: "flex items-start justify-center gap-4", children: filter?.arguments.map((e, i) => {
69
109
  const _filter = filters?.find((f) => f.code === filter.code);
70
110
  const argument = _filter?.args.find((a) => a.name === e.name);
71
- return argument?.ui?.component === 'facet-value-form-input' ? (_jsx(FacetsSelector, { value: JSON.parse(filter?.arguments[i].value), onChange: (e) => {
111
+ return getArgumentComponent(argument) === 'facet-value-form-input' ? (_jsx(FacetsSelector, { value: parseStringArrayValue(filter?.arguments[i].value), onChange: (e) => {
72
112
  filter.arguments[i] = { name: argument?.name || '', value: JSON.stringify(e) };
73
113
  handleFiltersValueChange(index, filter?.code, filter.arguments);
74
- } })) : argument?.ui?.component === 'select-form-input' ? (_jsx(SimpleSelect, { label: argument?.label || argument.name, value: filter?.arguments[i].value, onValueChange: (e) => {
114
+ } })) : getArgumentComponent(argument) === 'select-form-input' ? (_jsx(SimpleSelect, { label: argument?.label || argument?.name || '', value: filter?.arguments[i].value, onValueChange: (e) => {
75
115
  filter.arguments[i] = { name: argument?.name || '', value: e };
76
116
  handleFiltersValueChange(index, filter?.code, filter.arguments);
77
- }, options: (argument?.ui?.options).map((o) => ({
117
+ }, options: getArgumentOptions(argument).map((o) => ({
78
118
  label: o.value,
79
119
  value: o.value,
80
- })) || [] }, i)) : argument?.ui?.component === 'combination-mode-form-input' ? (_jsx(CombinationMode, { label: t('details.filters.labels.arguments.combination-mode'), value: filter?.arguments[i].value, onChange: (e) => {
120
+ })) || [] }, i)) : getArgumentComponent(argument) === 'combination-mode-form-input' ? (_jsx(CombinationMode, { label: t('details.filters.labels.arguments.combination-mode'), value: filter?.arguments[i].value, onChange: (e) => {
81
121
  filter.arguments[i] = { name: argument?.name || '', value: e };
82
122
  handleFiltersValueChange(index, filter?.code, filter.arguments);
83
- } })) : argument?.ui?.component === 'product-multi-form-input' ? (_jsx(VariantsSelector, { type: argument?.ui?.selectionMode, label: argument?.label || argument.name, value: JSON.parse(filter?.arguments[i].value), onChange: (e) => {
123
+ } })) : getArgumentComponent(argument) === 'product-multi-form-input' ? (_jsx(VariantsSelector, { type: argument?.ui?.selectionMode, label: argument?.label || argument?.name || '', value: parseStringArrayValue(filter?.arguments[i].value), onChange: (e) => {
84
124
  filter.arguments[i] = { name: argument?.name || '', value: JSON.stringify(e) };
85
125
  handleFiltersValueChange(index, filter?.code, filter.arguments);
86
126
  } })) : argument?.type === 'int' ? (_jsx("div", { className: "flex basis-full", children: _jsx(Input, { type: "number", step: 0.01, label: argument?.label || argument.name, value: filter?.arguments[i].value, onChange: (e) => {
@@ -2,5 +2,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useTranslation, CardIcons, CustomCard, Input } from '@deenruv/react-ui-devkit';
3
3
  export const PersonalDataCard = ({ setField, state }) => {
4
4
  const { t } = useTranslation('customers');
5
- return (_jsx(CustomCard, { title: t('personalData.header'), icon: _jsx(CardIcons.basic, {}), color: "blue", children: _jsxs("div", { className: "flex flex-col gap-4", children: [_jsx("div", { className: "flex w-1/4 gap-3", children: _jsx(Input, { label: t('personalData.title'), value: state.title?.value, onChange: (e) => setField('title', e.target.value) }) }), _jsxs("div", { className: "flex items-start gap-3", children: [_jsx(Input, { label: t('personalData.firstName'), value: state.firstName?.value, onChange: (e) => setField('firstName', e.target.value), errors: state.firstName?.errors, required: true }), _jsx(Input, { label: t('personalData.lastName'), value: state.lastName?.value, onChange: (e) => setField('lastName', e.target.value), errors: state.lastName?.errors, required: true })] }), _jsxs("div", { className: "flex items-start gap-3", children: [_jsx(Input, { label: t('personalData.emailAddress'), value: state.emailAddress?.value, onChange: (e) => setField('emailAddress', e.target.value), errors: state.emailAddress?.errors, required: true }), _jsx(Input, { label: t('personalData.phoneNumber'), value: state.phoneNumber?.value, onChange: (e) => setField('phoneNumber', e.target.value) })] })] }) }));
5
+ const getFieldErrors = (fieldName) => state.formState.errors?.[fieldName]?.message ? [state.formState.errors[fieldName].message] : undefined;
6
+ return (_jsx(CustomCard, { title: t('personalData.header'), icon: _jsx(CardIcons.basic, {}), color: "blue", children: _jsxs("div", { className: "flex flex-col gap-4", children: [_jsx("div", { className: "flex w-1/4 gap-3", children: _jsx(Input, { label: t('personalData.title'), value: state.watch('title') ?? undefined, onChange: (e) => setField('title', e.target.value) }) }), _jsxs("div", { className: "flex items-start gap-3", children: [_jsx(Input, { label: t('personalData.firstName'), value: state.watch('firstName') ?? undefined, onChange: (e) => setField('firstName', e.target.value), errors: getFieldErrors('firstName'), required: true }), _jsx(Input, { label: t('personalData.lastName'), value: state.watch('lastName') ?? undefined, onChange: (e) => setField('lastName', e.target.value), errors: getFieldErrors('lastName'), required: true })] }), _jsxs("div", { className: "flex items-start gap-3", children: [_jsx(Input, { label: t('personalData.emailAddress'), value: state.watch('emailAddress') ?? undefined, onChange: (e) => setField('emailAddress', e.target.value), errors: getFieldErrors('emailAddress'), required: true }), _jsx(Input, { label: t('personalData.phoneNumber'), value: state.watch('phoneNumber') ?? undefined, onChange: (e) => setField('phoneNumber', e.target.value) })] })] }) }));
6
7
  };
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useMemo, useState } from 'react';
3
- import { Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Input, PageBlock, Separator, Switch, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, usePluginStore, } from '@deenruv/react-ui-devkit';
3
+ import { Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Input, PageBlock, Separator, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, usePluginStore, } from '@deenruv/react-ui-devkit';
4
4
  import { BarChart3, Box, Check, ChevronDown, Code2, Cog, FileCode, Filter, Grid3X3, Info, LayoutDashboard, List, MessageSquare, Puzzle, RefreshCw, Search, Settings, Table, X, } from 'lucide-react';
5
5
  const getCategoryIcon = (category) => {
6
6
  switch (category) {
@@ -33,7 +33,7 @@ const getStatusBadge = (status) => {
33
33
  }
34
34
  };
35
35
  export const Extensions = () => {
36
- const { allPlugins: _plugins, changePluginStatus } = usePluginStore();
36
+ const { allPlugins: _plugins } = usePluginStore();
37
37
  const [searchTerm, setSearchTerm] = useState('');
38
38
  const [categoryFilter, setCategoryFilter] = useState('all');
39
39
  const [statusFilter, setStatusFilter] = useState('all');
@@ -65,9 +65,5 @@ export const Extensions = () => {
65
65
  setCategoryFilter('all');
66
66
  setStatusFilter('all');
67
67
  setSearchTerm('');
68
- }, children: "Reset filters" })] })) : view === 'grid' ? (_jsx("div", { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3", children: filteredPlugins.map((plugin) => (_jsxs(Card, { className: "overflow-hidden", children: [_jsxs(CardHeader, { className: "pb-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "rounded-md bg-primary/10 p-2", children: getCategoryIcon(plugin.category) }), _jsx(CardTitle, { className: "text-lg", children: plugin.name })] }), getStatusBadge(plugin.status)] }), _jsx(CardDescription, { className: "mt-2", children: plugin.description })] }), _jsxs(CardContent, { className: "pb-2", children: [_jsxs("div", { className: "grid grid-cols-2 gap-2 text-sm", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-muted-foreground", children: "Version:" }), _jsx("span", { children: plugin.version })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-muted-foreground", children: "Author:" }), _jsx("span", { children: plugin.author })] })] }), _jsx(Separator, { className: "my-3" }), _jsxs("div", { className: "grid grid-cols-4 gap-2 text-center text-sm", children: [_jsxs("div", { children: [_jsx("div", { className: "font-medium", children: plugin.tables?.length || 0 }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Tables" })] }), _jsxs("div", { children: [_jsx("div", { className: "font-medium", children: plugin.tabs?.length || 0 }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Tabs" })] }), _jsxs("div", { children: [_jsx("div", { className: "font-medium", children: plugin.components?.length || 0 }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Components" })] }), _jsxs("div", { children: [_jsx("div", { className: "font-medium", children: plugin.widgets?.length || 0 }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Widgets" })] })] })] }), _jsxs(CardFooter, { className: "flex justify-between pt-2", children: [_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", className: "size-8", children: _jsx(Info, { className: "size-4" }) }) }), _jsx(TooltipContent, { children: "View details" })] }) }), _jsx("div", { className: "flex gap-2", children: _jsx(Switch, { checked: plugin.status === 'active', onCheckedChange: (checked) => {
69
- changePluginStatus(plugin.name, checked ? 'active' : 'inactive');
70
- } }) })] })] }, plugin.name))) })) : (_jsxs("div", { className: "rounded-md border", children: [_jsxs("div", { className: "grid grid-cols-12 gap-4 p-4 font-medium", children: [_jsx("div", { className: "col-span-4", children: "Name" }), _jsx("div", { className: "col-span-3", children: "Author" }), _jsx("div", { className: "col-span-1 text-center", children: "Version" }), _jsx("div", { className: "col-span-1 text-center", children: "Status" }), _jsx("div", { className: "col-span-3 text-right", children: "Actions" })] }), _jsx(Separator, {}), filteredPlugins.map((plugin, index) => (_jsxs("div", { children: [_jsxs("div", { className: "grid grid-cols-12 items-center gap-4 p-4", children: [_jsx("div", { className: "col-span-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "rounded-md bg-primary/10 p-2", children: getCategoryIcon(plugin.category) }), _jsxs("div", { children: [_jsx("div", { className: "font-medium", children: plugin.name }), _jsx("div", { className: "text-xs text-muted-foreground", children: plugin.description })] })] }) }), _jsx("div", { className: "col-span-3", children: plugin.author }), _jsx("div", { className: "col-span-1 text-center", children: plugin.version }), _jsx("div", { className: "col-span-1 text-center", children: getStatusBadge(plugin.status) }), _jsxs("div", { className: "col-span-3 flex justify-end gap-2", children: [_jsxs(Button, { variant: "outline", size: "sm", children: [_jsx(Settings, { className: "mr-2 size-3" }), "Configure"] }), _jsx(Switch, { checked: plugin.status === 'active', onCheckedChange: (checked) => {
71
- changePluginStatus(plugin.name, checked ? 'active' : 'inactive');
72
- } })] })] }), index < filteredPlugins.length - 1 && _jsx(Separator, {})] }, plugin.name)))] }))] }) }));
68
+ }, children: "Reset filters" })] })) : view === 'grid' ? (_jsx("div", { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3", children: filteredPlugins.map((plugin) => (_jsxs(Card, { className: "overflow-hidden", children: [_jsxs(CardHeader, { className: "pb-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "rounded-md bg-primary/10 p-2", children: getCategoryIcon(plugin.category) }), _jsx(CardTitle, { className: "text-lg", children: plugin.name })] }), getStatusBadge(plugin.status)] }), _jsx(CardDescription, { className: "mt-2", children: plugin.description })] }), _jsxs(CardContent, { className: "pb-2", children: [_jsxs("div", { className: "grid grid-cols-2 gap-2 text-sm", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-muted-foreground", children: "Version:" }), _jsx("span", { children: plugin.version })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-muted-foreground", children: "Author:" }), _jsx("span", { children: plugin.author })] })] }), _jsx(Separator, { className: "my-3" }), _jsxs("div", { className: "grid grid-cols-4 gap-2 text-center text-sm", children: [_jsxs("div", { children: [_jsx("div", { className: "font-medium", children: plugin.tables?.length || 0 }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Tables" })] }), _jsxs("div", { children: [_jsx("div", { className: "font-medium", children: plugin.tabs?.length || 0 }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Tabs" })] }), _jsxs("div", { children: [_jsx("div", { className: "font-medium", children: plugin.components?.length || 0 }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Components" })] }), _jsxs("div", { children: [_jsx("div", { className: "font-medium", children: plugin.widgets?.length || 0 }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Widgets" })] })] })] }), _jsx(CardFooter, { className: "flex justify-between pt-2", children: _jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", className: "size-8", children: _jsx(Info, { className: "size-4" }) }) }), _jsx(TooltipContent, { children: "View details" })] }) }) })] }, plugin.name))) })) : (_jsxs("div", { className: "rounded-md border", children: [_jsxs("div", { className: "grid grid-cols-12 gap-4 p-4 font-medium", children: [_jsx("div", { className: "col-span-4", children: "Name" }), _jsx("div", { className: "col-span-3", children: "Author" }), _jsx("div", { className: "col-span-1 text-center", children: "Version" }), _jsx("div", { className: "col-span-1 text-center", children: "Status" }), _jsx("div", { className: "col-span-3 text-right", children: "Actions" })] }), _jsx(Separator, {}), filteredPlugins.map((plugin, index) => (_jsxs("div", { children: [_jsxs("div", { className: "grid grid-cols-12 items-center gap-4 p-4", children: [_jsx("div", { className: "col-span-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "rounded-md bg-primary/10 p-2", children: getCategoryIcon(plugin.category) }), _jsxs("div", { children: [_jsx("div", { className: "font-medium", children: plugin.name }), _jsx("div", { className: "text-xs text-muted-foreground", children: plugin.description })] })] }) }), _jsx("div", { className: "col-span-3", children: plugin.author }), _jsx("div", { className: "col-span-1 text-center", children: plugin.version }), _jsx("div", { className: "col-span-1 text-center", children: getStatusBadge(plugin.status) }), _jsx("div", { className: "col-span-3 flex justify-end gap-2", children: _jsxs(Button, { variant: "outline", size: "sm", children: [_jsx(Settings, { className: "mr-2 size-3" }), "Configure"] }) })] }), index < filteredPlugins.length - 1 && _jsx(Separator, {})] }, plugin.name)))] }))] }) }));
73
69
  };
@@ -1,17 +1,50 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect } from 'react';
3
- import { useParams } from 'react-router';
2
+ import { useEffect, useState } from 'react';
3
+ import { useNavigate, useParams } from 'react-router';
4
4
  import { OrderSummary, RealizationCard, CustomerSelectCard, AddressCard, ShippingMethod, TaxSummary, OrderHistory, ProductsCard, TopActions, Payments, SurchargeCard, } from "./_components";
5
- import { useTranslation, useOrder, ORDER_STATE, EntityCustomFields, LoadingMask, DetailViewMarker, } from '@deenruv/react-ui-devkit';
5
+ import { useTranslation, useOrder, ORDER_STATE, EntityCustomFields, LoadingMask, DetailViewMarker, apiClient, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Routes, } from '@deenruv/react-ui-devkit';
6
6
  import { PromotionsList } from "./_components/PromotionsList.js";
7
+ import { toast } from 'sonner';
8
+ const createDraftOrder = async () => {
9
+ const response = await apiClient('mutation')({
10
+ createDraftOrder: { id: true },
11
+ });
12
+ return response.createDraftOrder.id;
13
+ };
14
+ const DraftOrderCreateStart = () => {
15
+ const { t } = useTranslation('orders');
16
+ const navigate = useNavigate();
17
+ const [isCreating, setIsCreating] = useState(false);
18
+ const onCreate = async () => {
19
+ try {
20
+ setIsCreating(true);
21
+ const id = await createDraftOrder();
22
+ if (!id) {
23
+ toast.error(t('createDraftOrder.error'));
24
+ return;
25
+ }
26
+ navigate(Routes.orders.to(id), { viewTransition: true });
27
+ }
28
+ catch {
29
+ toast.error(t('createDraftOrder.error'));
30
+ }
31
+ finally {
32
+ setIsCreating(false);
33
+ }
34
+ };
35
+ return (_jsx("main", { className: "my-4", children: _jsx("div", { className: "mx-auto flex min-h-[70vh] w-full max-w-[720px] items-center px-4 2xl:px-8", children: _jsxs(Card, { className: "w-full", children: [_jsxs(CardHeader, { className: "space-y-2 text-center sm:text-left", children: [_jsx(CardTitle, { children: t('createDraftOrder.title') }), _jsx(CardDescription, { children: t('createDraftOrder.description') })] }), _jsx(CardContent, { children: _jsxs("div", { className: "flex flex-col-reverse gap-3 sm:flex-row sm:justify-end", children: [_jsx(Button, { variant: "outline", onClick: () => navigate(Routes.orders.list, { viewTransition: true }), disabled: isCreating, children: t('createDraftOrder.cancelButton') }), _jsx(Button, { onClick: onCreate, disabled: isCreating, children: isCreating ? (_jsxs("span", { className: "flex items-center gap-2", children: [_jsx("span", { className: "size-4 animate-spin rounded-full border-2 border-current border-r-transparent" }), t('createDraftOrder.creating')] })) : (t('createDraftOrder.startButton')) })] }) })] }) }) }));
36
+ };
7
37
  export const OrdersDetailPage = () => {
8
38
  const { id } = useParams();
9
39
  const { t } = useTranslation('orders');
10
40
  const { order, mode, fetchOrder, loading } = useOrder();
41
+ const isCreateRoute = id === 'new';
11
42
  useEffect(() => {
12
- if (id)
43
+ if (id && !isCreateRoute)
13
44
  fetchOrder(id);
14
- }, [id]);
45
+ }, [id, isCreateRoute]);
46
+ if (isCreateRoute)
47
+ return _jsx(DraftOrderCreateStart, {});
15
48
  if (loading)
16
49
  return _jsx(LoadingMask, {});
17
50
  if (!order || !mode) {
@@ -4,15 +4,8 @@ import { Routes, apiClient, DetailList, deepMerge, OrderStateBadge, priceFormatt
4
4
  import { useCallback } from 'react';
5
5
  import { useShallow } from 'zustand/react/shallow';
6
6
  import { useNavigate } from 'react-router';
7
- import { toast } from 'sonner';
8
7
  const tableId = 'orders-list-view';
9
8
  const { selector } = ListLocations[tableId];
10
- const createDraftOrder = async () => {
11
- const response = await apiClient('mutation')({
12
- createDraftOrder: { id: true },
13
- });
14
- return response.createDraftOrder.id;
15
- };
16
9
  const fetch = async ({ page, perPage, filter, filterOperator, sort }, additionalSelector) => {
17
10
  const response = await apiClient('query')({
18
11
  ['orders']: [
@@ -71,15 +64,7 @@ export const OrdersListPage = () => {
71
64
  },
72
65
  },
73
66
  ], detailLinkColumn: "id", searchFields: ['code', 'customerLastName'], entityName: "Order", route: {
74
- create: () => {
75
- createDraftOrder()
76
- .then((id) => {
77
- if (!id)
78
- toast.error('Failed to create draft order');
79
- navigate(Routes.orders.to(id), { viewTransition: true });
80
- })
81
- .catch(() => toast.error('Failed to create draft order'));
82
- },
67
+ create: () => navigate(Routes.orders.new, { viewTransition: true }),
83
68
  edit: (id) => navigate(Routes.orders.to(id), { viewTransition: true }),
84
69
  }, tableId: tableId, fetch: fetch, createPermissions: [Permission.CreateOrder], deletePermissions: [Permission.DeleteOrder], hideColumns: ['active', 'totalQuantity', 'currencyCode'], additionalColumns: [
85
70
  {
@@ -1,8 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
- import { Button, Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, DropdownMenuItem, useOrder, } from '@deenruv/react-ui-devkit';
3
+ import { Button, Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, DropdownMenuItem, useOrder, useTranslation, } from '@deenruv/react-ui-devkit';
4
4
  import { Undo2 } from 'lucide-react';
5
- import { useTranslation } from 'react-i18next';
6
5
  import { ProductsTable } from "./";
7
6
  import { RefundPaymentCard } from "./RefundPaymentCard.js";
8
7
  export const CancelAndRefundDialog = ({ refundReason, setRefundReason, onConfirm, }) => {
@@ -3,7 +3,7 @@ import { Button, Dialog, DialogContent, DialogTitle, ScrollArea, useOrder, useSe
3
3
  import { useState } from 'react';
4
4
  import { useShallow } from 'zustand/react/shallow';
5
5
  export const OrderLineCustomFields = ({ line, order, mode }) => {
6
- const { setModifiedOrder } = useOrder(({ setModifiedOrder }) => ({ setModifiedOrder }));
6
+ const setModifiedOrder = useOrder(({ setModifiedOrder }) => setModifiedOrder);
7
7
  const orderLineCustomFields = useServer(useShallow((p) => p.serverConfig?.entityCustomFields?.find((el) => el.entityName === 'OrderLine')?.customFields || []));
8
8
  const { t } = useTranslation('common');
9
9
  const [open, setOpen] = useState(false);
@@ -1,9 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { OrderLineCustomFields } from "./OrderLineCustomFields.js";
3
- import { Checkbox, ImageWithPreview, Input, Label, priceFormatter, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, useOrder, } from '@deenruv/react-ui-devkit';
3
+ import { Checkbox, ImageWithPreview, Input, Label, priceFormatter, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, useOrder, useTranslation, } from '@deenruv/react-ui-devkit';
4
4
  import { Tag } from 'lucide-react';
5
5
  import { useCallback } from 'react';
6
- import { useTranslation } from 'react-i18next';
7
6
  export const ProductsTable = ({ setRefundLines, refundLines }) => {
8
7
  const { t } = useTranslation('orders');
9
8
  const { mode, currentOrder } = useOrder();
@@ -1,6 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Checkbox, CustomCard, Input, Label, priceFormatter, Textarea, useOrder } from '@deenruv/react-ui-devkit';
3
- import { useTranslation } from 'react-i18next';
2
+ import { Checkbox, CustomCard, Input, Label, priceFormatter, Textarea, useOrder, useTranslation, } from '@deenruv/react-ui-devkit';
4
3
  export const RefundPaymentCard = ({ refundReason, setRefundReason, cancelShipping, setCancelShipping, refundAmount, setRefundAmount, }) => {
5
4
  const { t } = useTranslation('orders');
6
5
  const { modifiedOrder } = useOrder();
@@ -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)