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