@deenruv/admin-dashboard 1.0.0
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/LICENSE +23 -0
- package/README.md +30 -0
- package/dist/@types/resources.js +51 -0
- package/dist/DeenruvAdminPanel.js +118 -0
- package/dist/DeenruvDeveloperIndicator.js +57 -0
- package/dist/components/Aexol.js +4 -0
- package/dist/components/BrandLogo.js +22 -0
- package/dist/components/CanLeaveRouteDialog.js +8 -0
- package/dist/components/DataTable.js +13 -0
- package/dist/components/DeleteDialog.js +6 -0
- package/dist/components/DuplicateEntry.js +46 -0
- package/dist/components/GlobalSearch.js +134 -0
- package/dist/components/History/AddEntryForm.js +29 -0
- package/dist/components/History/DeleteEntryDialog.js +25 -0
- package/dist/components/History/EditEntryDialog.js +32 -0
- package/dist/components/History/History.js +13 -0
- package/dist/components/History/ModifyHistoryInfo.js +6 -0
- package/dist/components/History/Timeline.js +53 -0
- package/dist/components/History/index.js +5 -0
- package/dist/components/Menu/ActiveAdmins.js +24 -0
- package/dist/components/Menu/ChannelSwitcher.js +20 -0
- package/dist/components/Menu/LanguagesDropdown.js +26 -0
- package/dist/components/Menu/Navigation.js +270 -0
- package/dist/components/Menu/NavigationFooter.js +12 -0
- package/dist/components/Menu/Notifications.js +90 -0
- package/dist/components/Menu/index.js +67 -0
- package/dist/components/index.js +6 -0
- package/dist/graphql/base.js +95 -0
- package/dist/graphql/collections.js +94 -0
- package/dist/graphql/draft_order.js +307 -0
- package/dist/graphql/orders.js +157 -0
- package/dist/graphql/products.js +230 -0
- package/dist/graphql/scalars.js +22 -0
- package/dist/i18.js +13 -0
- package/dist/index.css +161 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +1 -0
- package/dist/locales/en/admins.json +57 -0
- package/dist/locales/en/assets.json +69 -0
- package/dist/locales/en/channels.json +66 -0
- package/dist/locales/en/collections.json +145 -0
- package/dist/locales/en/common.json +1127 -0
- package/dist/locales/en/countries.json +50 -0
- package/dist/locales/en/customerGroups.json +6 -0
- package/dist/locales/en/customers.json +109 -0
- package/dist/locales/en/dashboard.json +34 -0
- package/dist/locales/en/facets.json +78 -0
- package/dist/locales/en/globalSettings.json +15 -0
- package/dist/locales/en/index.js +52 -0
- package/dist/locales/en/orders.json +932 -0
- package/dist/locales/en/paymentMethods.json +59 -0
- package/dist/locales/en/permissions.json +94 -0
- package/dist/locales/en/products.json +194 -0
- package/dist/locales/en/promotions.json +65 -0
- package/dist/locales/en/roles.json +59 -0
- package/dist/locales/en/sellers.json +41 -0
- package/dist/locales/en/shippingMethods.json +84 -0
- package/dist/locales/en/stockLocations.json +40 -0
- package/dist/locales/en/system.json +41 -0
- package/dist/locales/en/table.json +201 -0
- package/dist/locales/en/taxCategories.json +47 -0
- package/dist/locales/en/taxRates.json +56 -0
- package/dist/locales/en/zones.json +47 -0
- package/dist/locales/index.js +2 -0
- package/dist/locales/pl/admins.json +57 -0
- package/dist/locales/pl/assets.json +69 -0
- package/dist/locales/pl/channels.json +66 -0
- package/dist/locales/pl/collections.json +145 -0
- package/dist/locales/pl/common.json +1128 -0
- package/dist/locales/pl/countries.json +50 -0
- package/dist/locales/pl/customerGroups.json +6 -0
- package/dist/locales/pl/customers.json +109 -0
- package/dist/locales/pl/dashboard.json +34 -0
- package/dist/locales/pl/facets.json +78 -0
- package/dist/locales/pl/globalSettings.json +15 -0
- package/dist/locales/pl/index.js +52 -0
- package/dist/locales/pl/orders.json +932 -0
- package/dist/locales/pl/paymentMethods.json +59 -0
- package/dist/locales/pl/permissions.json +94 -0
- package/dist/locales/pl/products.json +194 -0
- package/dist/locales/pl/promotions.json +65 -0
- package/dist/locales/pl/roles.json +59 -0
- package/dist/locales/pl/sellers.json +41 -0
- package/dist/locales/pl/shippingMethods.json +84 -0
- package/dist/locales/pl/stockLocations.json +40 -0
- package/dist/locales/pl/system.json +41 -0
- package/dist/locales/pl/table.json +201 -0
- package/dist/locales/pl/taxCategories.json +47 -0
- package/dist/locales/pl/taxRates.json +56 -0
- package/dist/locales/pl/zones.json +47 -0
- package/dist/notifications/OrderStatusNotification.js +47 -0
- package/dist/notifications/SystemStatusNotification.js +19 -0
- package/dist/pages/Custom404.js +5 -0
- package/dist/pages/LoginScreen.js +37 -0
- package/dist/pages/Root.js +252 -0
- package/dist/pages/admins/Detail.js +72 -0
- package/dist/pages/admins/List.js +56 -0
- package/dist/pages/admins/_components/AdminDetailView.js +34 -0
- package/dist/pages/admins/_components/RolesCard.js +31 -0
- package/dist/pages/admins/index.js +2 -0
- package/dist/pages/assets/List.js +82 -0
- package/dist/pages/assets/_components/Asset.js +114 -0
- package/dist/pages/assets/_components/AssetListView.js +29 -0
- package/dist/pages/assets/_components/EditAssetDialog.js +85 -0
- package/dist/pages/assets/_components/UploadAssetDialog.js +133 -0
- package/dist/pages/assets/_components/UploadProgress.js +20 -0
- package/dist/pages/assets/index.js +1 -0
- package/dist/pages/channels/Detail.js +149 -0
- package/dist/pages/channels/List.js +46 -0
- package/dist/pages/channels/_components/ChannelDetailView.js +51 -0
- package/dist/pages/channels/_components/DefaultsCard.js +25 -0
- package/dist/pages/channels/index.js +2 -0
- package/dist/pages/collections/Detail.js +61 -0
- package/dist/pages/collections/List.js +135 -0
- package/dist/pages/collections/_components/CollectionDetailView.js +52 -0
- package/dist/pages/collections/_components/CollectionProductVariantsDrawer.js +28 -0
- package/dist/pages/collections/_components/CombinationMode.js +6 -0
- package/dist/pages/collections/_components/ContentsCard.js +43 -0
- package/dist/pages/collections/_components/ContentsTable.js +102 -0
- package/dist/pages/collections/_components/DeleteCollectionsFromChannel.js +36 -0
- package/dist/pages/collections/_components/FacetsSelector.js +30 -0
- package/dist/pages/collections/_components/FiltersCard.js +101 -0
- package/dist/pages/collections/_components/MoveCollectionsToCollections.js +107 -0
- package/dist/pages/collections/_components/MoveEntityToChannels.js +115 -0
- package/dist/pages/collections/_components/PageHeader.js +11 -0
- package/dist/pages/collections/_components/SelectedCollectionsModal.js +22 -0
- package/dist/pages/collections/_components/VariantsSelector.js +43 -0
- package/dist/pages/collections/consts.js +7 -0
- package/dist/pages/collections/index.js +2 -0
- package/dist/pages/countries/Detail.js +57 -0
- package/dist/pages/countries/List.js +47 -0
- package/dist/pages/countries/_components/CountryDetailView.js +28 -0
- package/dist/pages/countries/index.js +2 -0
- package/dist/pages/customer-groups/Detail.js +76 -0
- package/dist/pages/customer-groups/List.js +41 -0
- package/dist/pages/customer-groups/_components/CustomerGroupsDetailView.js +21 -0
- package/dist/pages/customer-groups/index.js +2 -0
- package/dist/pages/customers/Detail.js +75 -0
- package/dist/pages/customers/List.js +60 -0
- package/dist/pages/customers/_components/Address.js +59 -0
- package/dist/pages/customers/_components/AddressDialog.js +56 -0
- package/dist/pages/customers/_components/AddressForm.js +77 -0
- package/dist/pages/customers/_components/AddressesCard.js +9 -0
- package/dist/pages/customers/_components/CustomerDetailSidebar.js +9 -0
- package/dist/pages/customers/_components/CustomerDetailView.js +41 -0
- package/dist/pages/customers/_components/CustomerGroupsCard.js +46 -0
- package/dist/pages/customers/_components/HistoryTab.js +65 -0
- package/dist/pages/customers/_components/OrdersTab.js +46 -0
- package/dist/pages/customers/_components/PersonalDataCard.js +6 -0
- package/dist/pages/customers/_components/VerifiedCard.js +6 -0
- package/dist/pages/customers/index.js +2 -0
- package/dist/pages/dashboard/Dashboard.js +10 -0
- package/dist/pages/dashboard/index.js +1 -0
- package/dist/pages/extensions/Extensions.js +74 -0
- package/dist/pages/extensions/index.js +1 -0
- package/dist/pages/facets/Detail.js +56 -0
- package/dist/pages/facets/List.js +48 -0
- package/dist/pages/facets/_components/AddFacetValueDialog.js +105 -0
- package/dist/pages/facets/_components/ColorSample.js +18 -0
- package/dist/pages/facets/_components/FacetDetailView.js +98 -0
- package/dist/pages/facets/index.js +2 -0
- package/dist/pages/global-settings/GlobalSettingsComponent.js +30 -0
- package/dist/pages/global-settings/index.js +71 -0
- package/dist/pages/index.js +25 -0
- package/dist/pages/orders/Detail.js +21 -0
- package/dist/pages/orders/List.js +132 -0
- package/dist/pages/orders/_components/AddPaymentDialog.js +50 -0
- package/dist/pages/orders/_components/AddressCard.js +260 -0
- package/dist/pages/orders/_components/CancelAndRefundDialog.js +29 -0
- package/dist/pages/orders/_components/ChangesRegister.js +68 -0
- package/dist/pages/orders/_components/ChangesRegisterTable.js +18 -0
- package/dist/pages/orders/_components/CouponCodesCard.js +85 -0
- package/dist/pages/orders/_components/CustomComponent.js +21 -0
- package/dist/pages/orders/_components/CustomerSelectCard.js +117 -0
- package/dist/pages/orders/_components/EditNoteButton.js +10 -0
- package/dist/pages/orders/_components/FulfillmentModal.js +85 -0
- package/dist/pages/orders/_components/LineItem.js +7 -0
- package/dist/pages/orders/_components/ManualOrderChangeModal.js +47 -0
- package/dist/pages/orders/_components/ModifyAcceptModal.js +22 -0
- package/dist/pages/orders/_components/ModifyingCard.js +77 -0
- package/dist/pages/orders/_components/OrderHistory.js +59 -0
- package/dist/pages/orders/_components/OrderLineActionModal/ActionQuantityPrice.js +58 -0
- package/dist/pages/orders/_components/OrderLineActionModal/index.js +7 -0
- package/dist/pages/orders/_components/OrderLineActionModal/types.js +1 -0
- package/dist/pages/orders/_components/OrderLineCustomFields.js +26 -0
- package/dist/pages/orders/_components/OrderSummary.js +10 -0
- package/dist/pages/orders/_components/PaymentMetadata.js +6 -0
- package/dist/pages/orders/_components/Payments.js +113 -0
- package/dist/pages/orders/_components/PossibleOrderStates.js +54 -0
- package/dist/pages/orders/_components/PriceChangedInfo.js +16 -0
- package/dist/pages/orders/_components/ProductsCard.js +308 -0
- package/dist/pages/orders/_components/ProductsTable.js +29 -0
- package/dist/pages/orders/_components/PromotionsList.js +73 -0
- package/dist/pages/orders/_components/RealizationCard.js +98 -0
- package/dist/pages/orders/_components/RefundCard.js +11 -0
- package/dist/pages/orders/_components/RefundPaymentCard.js +13 -0
- package/dist/pages/orders/_components/ShippingMethod.js +112 -0
- package/dist/pages/orders/_components/SpecialLineItem.js +7 -0
- package/dist/pages/orders/_components/SurchargeCard.js +99 -0
- package/dist/pages/orders/_components/SurchargeTable.js +8 -0
- package/dist/pages/orders/_components/TaxSummary.js +11 -0
- package/dist/pages/orders/_components/ToRealizationForm.js +64 -0
- package/dist/pages/orders/_components/TopActions.js +219 -0
- package/dist/pages/orders/_components/index.js +25 -0
- package/dist/pages/orders/index.js +2 -0
- package/dist/pages/payment-methods/Detail.js +67 -0
- package/dist/pages/payment-methods/List.js +41 -0
- package/dist/pages/payment-methods/_components/OptionsCard.js +60 -0
- package/dist/pages/payment-methods/_components/PaymentMethodDetailView.js +53 -0
- package/dist/pages/payment-methods/index.js +2 -0
- package/dist/pages/product-variants/Detail.js +4 -0
- package/dist/pages/product-variants/List.js +76 -0
- package/dist/pages/product-variants/index.js +2 -0
- package/dist/pages/products/Detail.js +75 -0
- package/dist/pages/products/List.js +79 -0
- package/dist/pages/products/_components/AddOptionGroupDialog.js +65 -0
- package/dist/pages/products/_components/AssetsCard.js +35 -0
- package/dist/pages/products/_components/BasicFieldsCard.js +6 -0
- package/dist/pages/products/_components/ChannelsCard.js +6 -0
- package/dist/pages/products/_components/CollectionsCard.js +6 -0
- package/dist/pages/products/_components/Container.js +4 -0
- package/dist/pages/products/_components/DiscountRatingCard.js +6 -0
- package/dist/pages/products/_components/FacetValuesCard.js +6 -0
- package/dist/pages/products/_components/ImagesCard.js +6 -0
- package/dist/pages/products/_components/OptionGroup.js +93 -0
- package/dist/pages/products/_components/OptionValueCard.js +59 -0
- package/dist/pages/products/_components/OptionsCard.js +38 -0
- package/dist/pages/products/_components/OptionsTab.js +40 -0
- package/dist/pages/products/_components/PriceCard.js +27 -0
- package/dist/pages/products/_components/ProductDetailSidebar.js +30 -0
- package/dist/pages/products/_components/ProductDetailView.js +54 -0
- package/dist/pages/products/_components/SettingsCard.js +6 -0
- package/dist/pages/products/_components/StockCard.js +40 -0
- package/dist/pages/products/_components/Variant.js +149 -0
- package/dist/pages/products/_components/VariantsTab.js +46 -0
- package/dist/pages/products/index.js +2 -0
- package/dist/pages/promotions/Detail.js +80 -0
- package/dist/pages/promotions/List.js +38 -0
- package/dist/pages/promotions/_components/ActionsCard.js +67 -0
- package/dist/pages/promotions/_components/BasicFieldsCard.js +6 -0
- package/dist/pages/promotions/_components/ConditionsCard.js +70 -0
- package/dist/pages/promotions/_components/CustomerGroupsSelector.js +25 -0
- package/dist/pages/promotions/_components/EnabledCard.js +6 -0
- package/dist/pages/promotions/_components/OptionsCard.js +6 -0
- package/dist/pages/promotions/_components/PromotionDetailSidebar.js +15 -0
- package/dist/pages/promotions/_components/PromotionDetailView.js +66 -0
- package/dist/pages/promotions/_components/PromotionFieldRender.js +4 -0
- package/dist/pages/promotions/index.js +2 -0
- package/dist/pages/roles/Detail.js +72 -0
- package/dist/pages/roles/List.js +70 -0
- package/dist/pages/roles/_components/PermissionsCard.js +7 -0
- package/dist/pages/roles/_components/PermissionsTable.js +42 -0
- package/dist/pages/roles/_components/RoleDetailView.js +49 -0
- package/dist/pages/roles/index.js +2 -0
- package/dist/pages/sellers/Detail.js +48 -0
- package/dist/pages/sellers/List.js +37 -0
- package/dist/pages/sellers/_components/SellerDetailView.js +20 -0
- package/dist/pages/sellers/index.js +2 -0
- package/dist/pages/shipping-methods/Detail.js +105 -0
- package/dist/pages/shipping-methods/List.js +40 -0
- package/dist/pages/shipping-methods/_components/CalculatorCard.js +79 -0
- package/dist/pages/shipping-methods/_components/CheckerCard.js +76 -0
- package/dist/pages/shipping-methods/_components/Lines.js +20 -0
- package/dist/pages/shipping-methods/_components/ShippingMethodDetailView.js +67 -0
- package/dist/pages/shipping-methods/_components/TestCard.js +62 -0
- package/dist/pages/shipping-methods/index.js +2 -0
- package/dist/pages/status/Status.js +6 -0
- package/dist/pages/status/_components/FilterToolbar.js +29 -0
- package/dist/pages/status/_components/Health.js +21 -0
- package/dist/pages/status/_components/JobResultPopover.js +8 -0
- package/dist/pages/status/_components/Jobs.js +199 -0
- package/dist/pages/status/_components/JsonExplorer.js +44 -0
- package/dist/pages/status/_components/JsonPopup.js +8 -0
- package/dist/pages/status/index.js +1 -0
- package/dist/pages/stock-locations/Detail.js +77 -0
- package/dist/pages/stock-locations/List.js +38 -0
- package/dist/pages/stock-locations/_components/StockLocationDetailView.js +22 -0
- package/dist/pages/stock-locations/index.js +2 -0
- package/dist/pages/tax-categories/Detail.js +85 -0
- package/dist/pages/tax-categories/List.js +46 -0
- package/dist/pages/tax-categories/_components/TaxCategoryDetailView.js +24 -0
- package/dist/pages/tax-categories/index.js +2 -0
- package/dist/pages/tax-rates/Detail.js +62 -0
- package/dist/pages/tax-rates/List.js +64 -0
- package/dist/pages/tax-rates/_components/TaxRateDetailView.js +64 -0
- package/dist/pages/tax-rates/index.js +2 -0
- package/dist/pages/zones/Detail.js +86 -0
- package/dist/pages/zones/List.js +52 -0
- package/dist/pages/zones/_components/ZoneDetailView.js +49 -0
- package/dist/pages/zones/index.js +2 -0
- package/dist/version.js +1 -0
- package/package.json +122 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { DeletionResult } from '@deenruv/admin-types';
|
|
3
|
+
import { useTranslation, Button, MultipleSelector, TableCell, TableRow, apiClient, } from '@deenruv/react-ui-devkit';
|
|
4
|
+
import { Trash } from 'lucide-react';
|
|
5
|
+
import { useCallback, useState } from 'react';
|
|
6
|
+
import { toast } from 'sonner';
|
|
7
|
+
export const OptionGroup = ({ group, productId, contentLanguage, optionsUsedByVariants, onActionCompleted, }) => {
|
|
8
|
+
const { t } = useTranslation('products');
|
|
9
|
+
const [state, setState] = useState(group.options
|
|
10
|
+
?.sort((a, b) => a.id.localeCompare(b.id))
|
|
11
|
+
.map((o) => ({ label: o.name, value: o.id, ...(optionsUsedByVariants.includes(o.id) && { fixed: true }) })));
|
|
12
|
+
const removeGroup = useCallback((optionGroupId) => {
|
|
13
|
+
if (!productId)
|
|
14
|
+
return;
|
|
15
|
+
apiClient('mutation')({
|
|
16
|
+
removeOptionGroupFromProduct: [
|
|
17
|
+
{ optionGroupId, productId: productId },
|
|
18
|
+
{ '...on Product': { id: true }, '...on ProductOptionInUseError': { message: true } },
|
|
19
|
+
],
|
|
20
|
+
})
|
|
21
|
+
.then(() => {
|
|
22
|
+
toast(t('toasts.deletionOptionSuccessToast'));
|
|
23
|
+
onActionCompleted();
|
|
24
|
+
})
|
|
25
|
+
.catch(() => {
|
|
26
|
+
toast.error(t('toasts.deletionOptionErrorToast'));
|
|
27
|
+
});
|
|
28
|
+
}, [productId, onActionCompleted, t]);
|
|
29
|
+
const addOption = useCallback((option, optionGroupId) => {
|
|
30
|
+
if (!productId)
|
|
31
|
+
return;
|
|
32
|
+
apiClient('mutation')({
|
|
33
|
+
createProductOption: [
|
|
34
|
+
{
|
|
35
|
+
input: {
|
|
36
|
+
code: option.label.replace(/\s/g, ''),
|
|
37
|
+
productOptionGroupId: optionGroupId,
|
|
38
|
+
translations: [{ languageCode: contentLanguage, name: option.label }],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{ id: true },
|
|
42
|
+
],
|
|
43
|
+
})
|
|
44
|
+
.then(() => {
|
|
45
|
+
toast(t('toasts.createOptionSuccessToast'));
|
|
46
|
+
onActionCompleted();
|
|
47
|
+
})
|
|
48
|
+
.catch(() => {
|
|
49
|
+
toast(t('toasts.createOptionErrorToast'));
|
|
50
|
+
});
|
|
51
|
+
}, [productId, contentLanguage, t]);
|
|
52
|
+
const deleteOption = useCallback((optionId) => {
|
|
53
|
+
if (!productId)
|
|
54
|
+
return;
|
|
55
|
+
apiClient('mutation')({
|
|
56
|
+
deleteProductOption: [
|
|
57
|
+
{
|
|
58
|
+
id: optionId,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
result: true,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
})
|
|
65
|
+
.then((resp) => {
|
|
66
|
+
if (resp.deleteProductOption.result === DeletionResult.DELETED) {
|
|
67
|
+
toast(t('toasts.createOptionSuccessToast'));
|
|
68
|
+
onActionCompleted();
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
toast.error(t('toasts.createOptionInUseToast'));
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
.catch(() => {
|
|
75
|
+
toast.error(t('toasts.createOptionErrorToast'));
|
|
76
|
+
});
|
|
77
|
+
}, [productId, contentLanguage, t]);
|
|
78
|
+
const handleChange = useCallback((currentOptions, optionGroupId) => {
|
|
79
|
+
const added = group?.options.length < currentOptions.length;
|
|
80
|
+
if (added) {
|
|
81
|
+
setState(currentOptions);
|
|
82
|
+
const newOption = currentOptions[currentOptions.length - 1];
|
|
83
|
+
addOption(newOption, optionGroupId);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const unpickedOption = state.find((o) => !currentOptions.includes(o));
|
|
87
|
+
if (unpickedOption) {
|
|
88
|
+
deleteOption(unpickedOption.value);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}, [group, addOption]);
|
|
92
|
+
return (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: group.name }), _jsx(TableCell, { children: _jsx(MultipleSelector, { className: "h-20", value: state, placeholder: t('optionsTab.placeholder'), onChange: (e) => handleChange(e, group.id), options: [], hideClearAllButton: true, creatable: true }) }), _jsx(TableCell, { className: "w-12", children: _jsx(Button, { size: 'icon', variant: 'outline', className: "size-8", onClick: () => removeGroup(group.id), children: _jsx(Trash, { size: 20, className: "text-red-600" }) }) })] }, group.id));
|
|
93
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect } from 'react';
|
|
3
|
+
import { Button, Input, Card, CardHeader, CardTitle, CardContent, apiClient, useGFFLP, setInArrayBy, EntityCustomFields, useTranslation, } from '@deenruv/react-ui-devkit';
|
|
4
|
+
import { toast } from 'sonner';
|
|
5
|
+
export const OptionValueCard = ({ productOption, onEdited, currentTranslationLng, optionGroupId, }) => {
|
|
6
|
+
const { t } = useTranslation('products');
|
|
7
|
+
const { state, setField } = useGFFLP('UpdateProductOptionInput', 'code', 'translations', 'customFields')({});
|
|
8
|
+
const translations = state?.translations?.value || [];
|
|
9
|
+
const currentTranslationValue = translations.find((v) => v.languageCode === currentTranslationLng);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
setField('code', productOption.code);
|
|
12
|
+
setField('translations', productOption.translations);
|
|
13
|
+
if ('customFields' in productOption) {
|
|
14
|
+
setField('customFields', productOption.customFields);
|
|
15
|
+
}
|
|
16
|
+
}, [productOption]);
|
|
17
|
+
const editOption = useCallback(() => {
|
|
18
|
+
if (productOption.id) {
|
|
19
|
+
console.log('INPUT', {
|
|
20
|
+
id: productOption.id,
|
|
21
|
+
code: state.code?.validatedValue,
|
|
22
|
+
customFields: state.customFields?.validatedValue,
|
|
23
|
+
translations: state.translations?.validatedValue,
|
|
24
|
+
});
|
|
25
|
+
return apiClient('mutation')({
|
|
26
|
+
updateProductOption: [
|
|
27
|
+
{
|
|
28
|
+
input: {
|
|
29
|
+
id: productOption.id,
|
|
30
|
+
code: state.code?.validatedValue,
|
|
31
|
+
customFields: state.customFields?.validatedValue,
|
|
32
|
+
translations: state.translations?.validatedValue,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{ id: true },
|
|
36
|
+
],
|
|
37
|
+
})
|
|
38
|
+
.then(() => {
|
|
39
|
+
toast(t('toasts.updateOptionSuccessToast'));
|
|
40
|
+
onEdited();
|
|
41
|
+
})
|
|
42
|
+
.catch(() => {
|
|
43
|
+
toast(t('toasts.updateOptionErrorToast'));
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}, [state, productOption, t, onEdited]);
|
|
47
|
+
return (_jsxs(Card, { className: "flex-grow basis-1/5", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "flex flex-row justify-between text-base", children: productOption.name }) }), _jsxs(CardContent, { children: [_jsx("div", { className: "flex flex-col justify-between gap-6", children: _jsxs("div", { className: "flex flex-col gap-3", children: [_jsx(Input, { label: "name", value: currentTranslationValue?.name ?? undefined, onChange: (e) => {
|
|
48
|
+
setField('translations', setInArrayBy(translations, (t) => t.languageCode !== currentTranslationLng, {
|
|
49
|
+
name: e.target.value,
|
|
50
|
+
languageCode: currentTranslationLng,
|
|
51
|
+
}));
|
|
52
|
+
} }), _jsx(Input, { label: "code", value: state.code?.value ?? undefined, onChange: (e) => {
|
|
53
|
+
setField('code', e.target.value);
|
|
54
|
+
} }), _jsx(EntityCustomFields, { entityName: "productOption", withoutBorder: true, id: productOption.id, currentLanguage: currentTranslationLng, initialValues: state && 'customFields' in state
|
|
55
|
+
? { customFields: state.customFields?.validatedValue }
|
|
56
|
+
: { customFields: {} }, onChange: (cf) => {
|
|
57
|
+
setField('customFields', cf);
|
|
58
|
+
}, additionalData: {} })] }) }), _jsx(Button, { size: 'sm', className: "mt-4", onClick: editOption, children: t('editOption') })] })] }));
|
|
59
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
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';
|
|
4
|
+
import { OptionGroupSelector } from "../../../graphql/products";
|
|
5
|
+
import { toast } from 'sonner';
|
|
6
|
+
export const OptionsCard = ({ optionGroups: options, productId, onChange, optionIds, createMode, }) => {
|
|
7
|
+
const { t } = useTranslation('products');
|
|
8
|
+
const [optionGroups, setOptionGroups] = useState();
|
|
9
|
+
const fetchOptionGroups = useCallback(async () => {
|
|
10
|
+
if (productId) {
|
|
11
|
+
const response = await apiClient('query')({
|
|
12
|
+
product: [
|
|
13
|
+
{
|
|
14
|
+
id: productId,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
optionGroups: OptionGroupSelector,
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
});
|
|
21
|
+
setOptionGroups(response.product?.optionGroups);
|
|
22
|
+
if (!response.product) {
|
|
23
|
+
toast.error(t('toasts.fetchProductErrorToast'));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}, [productId]);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
fetchOptionGroups();
|
|
29
|
+
}, [fetchOptionGroups]);
|
|
30
|
+
const handleOptionChange = useCallback((optionId, groupIdx) => {
|
|
31
|
+
const newState = [...(optionIds || [])];
|
|
32
|
+
newState[groupIdx] = optionId;
|
|
33
|
+
onChange(newState);
|
|
34
|
+
}, [optionIds, onChange]);
|
|
35
|
+
return (_jsx(CustomCard, { title: t('options'), icon: _jsx(CardIcons.options, {}), color: "orange", children: !createMode ? (_jsx(Table, { children: _jsx(TableBody, { children: options?.map((o) => (_jsxs(TableRow, { children: [_jsxs(TableCell, { className: "font-semibold capitalize", children: [o.group.name, ":"] }), _jsx(TableCell, { className: "capitalize", children: o.name })] }, o.name))) }) })) : (optionGroups?.map((group, i) => (_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("div", { className: "w-1/3 font-semibold", children: [group.name, ":"] }), _jsx("div", { className: "w-2/3", children: _jsxs(Select, { value: optionIds?.[i] || '', onValueChange: (e) => {
|
|
36
|
+
handleOptionChange(e, i);
|
|
37
|
+
}, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: t('addVariantDialog.selectOption') }) }), _jsx(SelectContent, { children: group.options.map((o) => (_jsx(SelectItem, { value: o.id, className: "capitalize", children: o.name }, o.id))) })] }) })] }, group.name)))) }));
|
|
38
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { OptionGroupSelector } from "../../../graphql/products";
|
|
3
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
4
|
+
import { Table, TableBody, TableHead, TableHeader, TableRow, useDetailView, apiClient, useSettings, EmptyState, CustomCard, CardIcons, Separator, useTranslation, } from '@deenruv/react-ui-devkit';
|
|
5
|
+
import { toast } from 'sonner';
|
|
6
|
+
import { AddOptionGroupDialog } from "./AddOptionGroupDialog";
|
|
7
|
+
import { OptionValueCard } from "./OptionValueCard";
|
|
8
|
+
import { OptionGroup } from "./OptionGroup";
|
|
9
|
+
import { Info } from 'lucide-react';
|
|
10
|
+
export const OptionsTab = () => {
|
|
11
|
+
const contentLng = useSettings((p) => p.translationsLanguage);
|
|
12
|
+
const { id, getMarker, setLoading } = useDetailView('products-detail-view', 'CreateProductInput');
|
|
13
|
+
const { t } = useTranslation('products');
|
|
14
|
+
const [optionGroups, setOptionGroups] = useState();
|
|
15
|
+
const [optionsUsedByVariants, setOptionsUsedByVariants] = useState([]);
|
|
16
|
+
const fetchOptionGroups = useCallback(async () => {
|
|
17
|
+
setLoading(true);
|
|
18
|
+
if (id) {
|
|
19
|
+
const response = await apiClient('query')({
|
|
20
|
+
product: [{ id }, { optionGroups: OptionGroupSelector, variants: { options: { id: true } } }],
|
|
21
|
+
});
|
|
22
|
+
setOptionGroups(response.product?.optionGroups);
|
|
23
|
+
setOptionsUsedByVariants(response.product?.variants.flatMap((v) => v.options.map((o) => o.id)) || []);
|
|
24
|
+
setLoading(false);
|
|
25
|
+
if (!response.product) {
|
|
26
|
+
toast.error(t('toasts.fetchProductErrorToast'));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}, [id]);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
fetchOptionGroups();
|
|
32
|
+
}, [fetchOptionGroups]);
|
|
33
|
+
return (_jsx("div", { className: "flex flex-col items-end", children: _jsxs("div", { className: "flex w-full flex-col gap-3", children: [getMarker(), _jsx(CustomCard, { title: t('optionGroups'), color: "purple", icon: _jsx(CardIcons.group, {}), upperRight: _jsx(AddOptionGroupDialog, { currentTranslationLng: contentLng, onSuccess: fetchOptionGroups, productId: id }), children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: t('optionsTab.groupName') }), _jsx(TableHead, { children: t('optionsTab.values') }), _jsx(TableHead, {})] }) }), _jsx(TableBody, { children: id && optionGroups?.length ? (optionGroups
|
|
34
|
+
?.sort((a, b) => a.id.localeCompare(b.id))
|
|
35
|
+
?.map((group) => (_jsx(OptionGroup, { contentLanguage: contentLng, group: group, productId: id, onActionCompleted: fetchOptionGroups, optionsUsedByVariants: optionsUsedByVariants })))) : (_jsx(EmptyState, { columnsLength: 2, title: t('optionsTab.emptyState.title'), description: t('optionsTab.emptyState.description') })) })] }) }), _jsx(Separator, {}), optionGroups
|
|
36
|
+
?.sort((a, b) => a.id.localeCompare(b.id))
|
|
37
|
+
?.map((oG) => (_jsx(CustomCard, { variant: "group", title: `${t('group')}: ${oG.name}`, icon: _jsx(CardIcons.default, {}), collapsed: true, notCollapsible: !oG.options.length, upperRight: !oG.options.length && (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx(Info, { className: "size-4" }), t('optionsTab.noOptionValues')] })), children: _jsx("div", { className: "grid grid-cols-4 gap-3", children: oG.options
|
|
38
|
+
?.sort((a, b) => a.id.localeCompare(b.id))
|
|
39
|
+
.map((o) => (_jsx(OptionValueCard, { currentTranslationLng: contentLng, productOption: o, optionGroupId: oG.id, onEdited: fetchOptionGroups }, o.id))) }) }, oG.id)))] }) }));
|
|
40
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
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, }) => {
|
|
5
|
+
const { t } = useTranslation('products');
|
|
6
|
+
const [taxCategories, setTaxCategories] = useState([]);
|
|
7
|
+
const [currentTaxCategory, setCurrentTaxCategory] = useState();
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
fetchTaxRates();
|
|
10
|
+
}, []);
|
|
11
|
+
const fetchTaxRates = useCallback(async () => {
|
|
12
|
+
const response = await apiClient('query')({
|
|
13
|
+
taxCategories: [{}, { items: { id: true, name: true } }],
|
|
14
|
+
taxRates: [{}, { items: { value: true, category: { id: true } } }],
|
|
15
|
+
});
|
|
16
|
+
const categoriesWithRates = response.taxCategories.items.map((c) => ({
|
|
17
|
+
...c,
|
|
18
|
+
value: response.taxRates.items.find((r) => r.category.id === c.id)?.value,
|
|
19
|
+
}));
|
|
20
|
+
setTaxCategories(categoriesWithRates);
|
|
21
|
+
}, []);
|
|
22
|
+
const handlePriceChange = useCallback((currencyCode, value) => {
|
|
23
|
+
const newPrices = priceValue?.map((p) => (p.currencyCode === currencyCode ? { ...p, price: value } : p));
|
|
24
|
+
onPriceChange(newPrices || []);
|
|
25
|
+
}, [priceValue, onPriceChange]);
|
|
26
|
+
return (_jsx(CustomCard, { title: t('details.price'), color: "rose", icon: _jsx(CardIcons.calc, {}), children: _jsxs("div", { className: "flex flex-col gap-y-4", children: [priceValue?.map((price) => (_jsx("div", { className: "flex items-center gap-x-2", children: _jsx(Input, { type: "currency", placeholder: t('price'), value: price.price, onChange: (e) => handlePriceChange(price.currencyCode, +e.target.value), step: 0.01, startAdornment: price.currencyCode }) }, price.currencyCode))), _jsxs(Select, { value: taxRateValue, onValueChange: onTaxRateChange, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Tax rate" }) }), _jsx(SelectContent, { children: taxCategories.map((tR) => (_jsx(SelectItem, { value: tR.id.toString(), children: tR.name }, tR.id))) })] }), currentTaxCategory?.value !== undefined && `${t('details.taxRateDescription')} ${currentTaxCategory?.value}%`] }) }));
|
|
27
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { EntityChannelManager, Routes, useDetailView } from '@deenruv/react-ui-devkit';
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
import { SettingsCard } from './SettingsCard';
|
|
5
|
+
import { CollectionsCard } from "./CollectionsCard";
|
|
6
|
+
import { FacetValuesCard } from "./FacetValuesCard";
|
|
7
|
+
import { useNavigate } from 'react-router-dom';
|
|
8
|
+
const PRODUCT_FORM_KEYS = [
|
|
9
|
+
'CreateProductInput',
|
|
10
|
+
'translations',
|
|
11
|
+
'assetIds',
|
|
12
|
+
'featuredAssetId',
|
|
13
|
+
'facetValueIds',
|
|
14
|
+
'enabled',
|
|
15
|
+
];
|
|
16
|
+
export const ProductDetailSidebar = ({ marker }) => {
|
|
17
|
+
const { form, entity } = useDetailView('products-detail-view', ...PRODUCT_FORM_KEYS);
|
|
18
|
+
const navigate = useNavigate();
|
|
19
|
+
const { base: { state, setField }, } = form;
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!entity) {
|
|
22
|
+
setField('facetValueIds', []);
|
|
23
|
+
setField('enabled', true);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
setField('facetValueIds', entity.facetValues.map((f) => f.id));
|
|
27
|
+
setField('enabled', entity.enabled);
|
|
28
|
+
}, [entity]);
|
|
29
|
+
return (_jsxs("div", { className: "flex w-full flex-col gap-4", children: [_jsx(SettingsCard, { enabledValue: state.enabled?.value ?? undefined, onEnabledChange: (e) => setField('enabled', e) }), _jsx(FacetValuesCard, { facetValuesIds: state.facetValueIds?.value ?? undefined, onChange: (e) => setField('facetValueIds', e) }), !!entity?.channels?.length && (_jsx(EntityChannelManager, { entity: "product", entityChannels: entity.channels, entityId: entity.id, onRemoveSuccess: () => navigate(Routes.products.list), entitySlug: entity.slug, entityName: entity.name })), !!entity?.collections?.length && _jsx(CollectionsCard, { collections: entity.collections })] }));
|
|
30
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useDetailView, DetailViewMarker, useSettings, setInArrayBy, EntityCustomFields, } from '@deenruv/react-ui-devkit';
|
|
3
|
+
import { useCallback, useEffect, useMemo } from 'react';
|
|
4
|
+
import { BasicFieldsCard } from './BasicFieldsCard';
|
|
5
|
+
import { AssetsCard } from './AssetsCard';
|
|
6
|
+
export const PRODUCT_FORM_KEYS = [
|
|
7
|
+
'CreateProductInput',
|
|
8
|
+
'translations',
|
|
9
|
+
'assetIds',
|
|
10
|
+
'featuredAssetId',
|
|
11
|
+
'facetValueIds',
|
|
12
|
+
'enabled',
|
|
13
|
+
'customFields',
|
|
14
|
+
];
|
|
15
|
+
export const ProductDetailView = () => {
|
|
16
|
+
const contentLng = useSettings((p) => p.translationsLanguage);
|
|
17
|
+
const selectedChannel = useSettings((p) => p.selectedChannel);
|
|
18
|
+
const { entity, id, form, loading, fetchEntity } = useDetailView('products-detail-view', ...PRODUCT_FORM_KEYS);
|
|
19
|
+
const { base: { setField, state }, } = form;
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
(async () => {
|
|
22
|
+
const res = await fetchEntity();
|
|
23
|
+
if (!res)
|
|
24
|
+
return;
|
|
25
|
+
setField('translations', res.translations);
|
|
26
|
+
setField('assetIds', res.assets.map((a) => a.id));
|
|
27
|
+
setField('featuredAssetId', res.featuredAsset?.id);
|
|
28
|
+
})();
|
|
29
|
+
}, [selectedChannel?.id, contentLng]);
|
|
30
|
+
const translations = state?.translations?.value || [];
|
|
31
|
+
const currentTranslationValue = useMemo(() => {
|
|
32
|
+
return translations.find((v) => v.languageCode === contentLng);
|
|
33
|
+
}, [translations, contentLng]);
|
|
34
|
+
const setTranslationField = useCallback((field, e) => {
|
|
35
|
+
setField('translations', setInArrayBy(translations, (t) => t.languageCode !== contentLng, {
|
|
36
|
+
...currentTranslationValue,
|
|
37
|
+
[field]: e,
|
|
38
|
+
languageCode: contentLng,
|
|
39
|
+
}));
|
|
40
|
+
}, [contentLng, translations]);
|
|
41
|
+
const handleAddAsset = useCallback((newId) => {
|
|
42
|
+
if (!newId)
|
|
43
|
+
return;
|
|
44
|
+
const currentIds = state.assetIds?.value || [];
|
|
45
|
+
setField('assetIds', [...currentIds, newId]);
|
|
46
|
+
}, [state.assetIds?.value, setField]);
|
|
47
|
+
return (_jsx("div", { children: _jsxs("div", { className: "flex w-full flex-col gap-4", children: [_jsx(BasicFieldsCard, { currentTranslationValue: currentTranslationValue, onChange: setTranslationField, errors: state.translations?.errors }), _jsx(DetailViewMarker, { position: 'products-detail-view' }), _jsx(EntityCustomFields, { id: id, entityName: "product", hideButton: true, onChange: (customFields, translations) => {
|
|
48
|
+
setField('customFields', customFields);
|
|
49
|
+
if (translations)
|
|
50
|
+
setField('translations', translations);
|
|
51
|
+
}, initialValues: entity && 'customFields' in entity
|
|
52
|
+
? { customFields: entity.customFields, translations: entity.translations }
|
|
53
|
+
: { customFields: {} } }), _jsx(AssetsCard, { onAddAsset: handleAddAsset, featuredAssetId: state.featuredAssetId?.value, assetsIds: state.assetIds?.value, onFeaturedAssetChange: (id) => setField('featuredAssetId', id), onAssetsChange: (ids) => setField('assetIds', ids) })] }) }));
|
|
54
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useTranslation, Label, Switch, CustomCard, CardIcons } from '@deenruv/react-ui-devkit';
|
|
3
|
+
export const SettingsCard = ({ onEnabledChange, enabledValue }) => {
|
|
4
|
+
const { t } = useTranslation('products');
|
|
5
|
+
return (_jsx(CustomCard, { title: t('details.settings'), color: "gray", icon: _jsx(CardIcons.options, {}), children: _jsxs("div", { className: "flex items-center space-x-2", children: [_jsx(Switch, { id: "product-enabled", checked: enabledValue, onCheckedChange: onEnabledChange }), _jsx(Label, { htmlFor: "product-enabled", children: t('enabled') })] }) }));
|
|
6
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuTrigger, DropdownMenuItem, Separator, apiClient, CustomCard, CardIcons, useTranslation, } from '@deenruv/react-ui-devkit';
|
|
3
|
+
import { GlobalFlag } from '@deenruv/admin-types';
|
|
4
|
+
import { MapPin } from 'lucide-react';
|
|
5
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
6
|
+
export const StockCard = ({ outOfStockThresholdValue, stockOnHandValue, useGlobalOutOfStockThresholdValue, trackInventoryValue, stockAllocated, stockLevelsValue, allStockLocations, onThresholdChange, onUseGlobalChange, onTrackInventoryChange, onStockOnHandChange, onStockLocationsChange, }) => {
|
|
7
|
+
const { t } = useTranslation('products');
|
|
8
|
+
const [stockLocations, setStockLocations] = useState([]);
|
|
9
|
+
const notUsedStockLocations = useMemo(() => stockLocations.filter((sL) => stockLevelsValue?.findIndex((v) => v.stockLocationId === sL.id) === -1), [stockLocations, stockLevelsValue]);
|
|
10
|
+
const fetchStockLocations = useCallback(async () => {
|
|
11
|
+
const response = await apiClient('query')({
|
|
12
|
+
stockLocations: [
|
|
13
|
+
{},
|
|
14
|
+
{
|
|
15
|
+
items: {
|
|
16
|
+
id: true,
|
|
17
|
+
name: true,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
});
|
|
22
|
+
setStockLocations(response.stockLocations.items);
|
|
23
|
+
}, []);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
fetchStockLocations();
|
|
26
|
+
}, [fetchStockLocations]);
|
|
27
|
+
const handleChangeStockLocation = useCallback((location) => {
|
|
28
|
+
const newLevels = stockLevelsValue ? [...stockLevelsValue] : [];
|
|
29
|
+
const elementIdx = newLevels.findIndex((l) => l.stockLocationId === location.stockLocationId);
|
|
30
|
+
if (elementIdx !== -1) {
|
|
31
|
+
newLevels[elementIdx].stockOnHand = location.stockOnHand;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
newLevels.push(location);
|
|
35
|
+
}
|
|
36
|
+
onStockLocationsChange(newLevels);
|
|
37
|
+
}, [onStockLocationsChange, stockLevelsValue]);
|
|
38
|
+
const getLocationAllocatedValue = useCallback((id) => allStockLocations?.find((e) => e.stockLocationId === id)?.stockAllocated, [allStockLocations]);
|
|
39
|
+
return (_jsx(CustomCard, { title: t('details.stockLevels'), color: "blue", icon: _jsx(CardIcons.stock, {}), children: _jsxs("div", { className: "flex flex-col gap-y-4", children: [_jsxs("div", { className: "flex items-start justify-start gap-x-4", children: [_jsx("div", { className: "w-1/2", children: _jsx(Input, { type: "number", placeholder: t('details.outThreshold'), label: t('details.outThreshold'), value: outOfStockThresholdValue ?? undefined, onChange: onThresholdChange }) }), _jsxs("div", { className: "flex flex-col gap-4", children: [_jsx(Label, { children: t('details.useGlobal') }), _jsx(Switch, { checked: useGlobalOutOfStockThresholdValue ?? undefined, onCheckedChange: onUseGlobalChange })] })] }), _jsxs("div", { className: "flex flex-col items-start justify-start gap-2", children: [_jsx(Label, { children: t('details.trackInventory') }), _jsxs(Select, { value: trackInventoryValue ?? undefined, onValueChange: (value) => onTrackInventoryChange(value), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: GlobalFlag.INHERIT, children: t('details.inherit') }), _jsx(SelectItem, { value: GlobalFlag.TRUE, children: t('details.track') }), _jsx(SelectItem, { value: GlobalFlag.FALSE, children: t('details.notTrack') })] })] })] }), _jsx(Separator, { orientation: "horizontal" }), _jsxs("div", { className: "flex items-start justify-start gap-4", children: [_jsx("div", { className: "w-1/2", children: _jsx(Input, { type: "number", label: t('details.defaultStock1'), value: stockOnHandValue ?? undefined, disabled: true }) }), _jsxs("div", { className: "flex w-1/2 flex-col gap-3", children: [_jsx(Label, { children: t('details.defaultStock2') }), stockAllocated] })] }), stockLevelsValue?.map((sL) => (_jsxs("div", { className: "flex items-start justify-start gap-4", children: [_jsx("div", { className: "w-1/2", children: _jsx(Input, { type: "number", label: `Stock location: ${stockLocations.find((l) => l.id === sL.stockLocationId)?.name}`, value: sL.stockOnHand, onChange: (e) => handleChangeStockLocation({ stockLocationId: sL.stockLocationId, stockOnHand: +e.target.value }) }) }), _jsxs("div", { className: "flex w-1/2 flex-col gap-3", children: [_jsx(Label, { children: "Allocated" }), getLocationAllocatedValue(sL.stockLocationId)] })] }, 'inputs' + sL.stockLocationId))), _jsx("div", { className: "flex", children: _jsxs(DropdownMenu, { modal: false, children: [_jsx(DropdownMenuTrigger, { asChild: true, disabled: !notUsedStockLocations.length, children: _jsx(Button, { size: 'sm', className: "mt-4", children: t('details.addStockLocation') }) }), _jsx(DropdownMenuContent, { className: "w-56", side: "bottom", align: "end", children: _jsx(DropdownMenuGroup, { children: notUsedStockLocations.map((sL) => (_jsxs(DropdownMenuItem, { onClick: () => handleChangeStockLocation({ stockLocationId: sL.id, stockOnHand: 0 }), children: [_jsx(MapPin, { className: "mr-2" }), sL.name] }, 'dropdown' + sL.id))) }) })] }) })] }) }));
|
|
40
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button, CardIcons, ConfirmationDialog, CustomCard, Input, apiClient, setInArrayBy, useGFFLP, EntityCustomFields, useTranslation, EntityChannelManager, } from '@deenruv/react-ui-devkit';
|
|
3
|
+
import { CurrencyCode } from '@deenruv/admin-types';
|
|
4
|
+
import { useCallback, useEffect } from 'react';
|
|
5
|
+
import { toast } from 'sonner';
|
|
6
|
+
import { AssetsCard } from "./AssetsCard";
|
|
7
|
+
import { PriceCard } from "./PriceCard";
|
|
8
|
+
import { StockCard } from "./StockCard";
|
|
9
|
+
import { OptionsCard } from "./OptionsCard";
|
|
10
|
+
import { FacetValuesCard } from "./FacetValuesCard";
|
|
11
|
+
export const Variant = ({ variant, currentTranslationLng, onActionCompleted, productId }) => {
|
|
12
|
+
const { t } = useTranslation('products');
|
|
13
|
+
const { state, setField } = useGFFLP('UpdateProductVariantInput', 'translations', 'price', 'prices', 'sku', 'assetIds', 'featuredAssetId', 'taxCategoryId', 'stockLevels', 'stockOnHand', 'outOfStockThreshold', 'useGlobalOutOfStockThreshold', 'trackInventory', 'facetValueIds', 'optionIds', 'customFields')({});
|
|
14
|
+
const translations = state?.translations?.value || [];
|
|
15
|
+
const currentTranslationValue = translations.find((v) => v.languageCode === currentTranslationLng);
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (!variant)
|
|
18
|
+
return;
|
|
19
|
+
setField('sku', variant.sku);
|
|
20
|
+
setField('price', variant.price);
|
|
21
|
+
setField('prices', variant.prices);
|
|
22
|
+
setField('translations', variant.translations);
|
|
23
|
+
setField('assetIds', variant.assets.map((a) => a.id));
|
|
24
|
+
setField('featuredAssetId', variant.featuredAsset?.id);
|
|
25
|
+
setField('taxCategoryId', variant.taxCategory.id);
|
|
26
|
+
setField('stockLevels', variant.stockLevels.map((sL) => ({ stockLocationId: sL.stockLocationId, stockOnHand: sL.stockOnHand })));
|
|
27
|
+
setField('stockOnHand', variant.stockOnHand);
|
|
28
|
+
setField('outOfStockThreshold', variant.outOfStockThreshold);
|
|
29
|
+
setField('useGlobalOutOfStockThreshold', variant.useGlobalOutOfStockThreshold);
|
|
30
|
+
setField('trackInventory', variant.trackInventory);
|
|
31
|
+
setField('facetValueIds', variant.facetValues.map((f) => f.id));
|
|
32
|
+
}, [variant]);
|
|
33
|
+
const createVariant = useCallback(() => {
|
|
34
|
+
if (productId && state.sku?.validatedValue && state.translations?.validatedValue)
|
|
35
|
+
return apiClient('mutation')({
|
|
36
|
+
createProductVariants: [
|
|
37
|
+
{
|
|
38
|
+
input: [
|
|
39
|
+
{
|
|
40
|
+
productId,
|
|
41
|
+
translations: state.translations?.validatedValue,
|
|
42
|
+
// price: +state.price?.validatedValue,
|
|
43
|
+
prices: state.prices?.validatedValue,
|
|
44
|
+
sku: state.sku?.validatedValue,
|
|
45
|
+
assetIds: state.assetIds?.validatedValue,
|
|
46
|
+
featuredAssetId: state.featuredAssetId?.validatedValue,
|
|
47
|
+
outOfStockThreshold: state.outOfStockThreshold?.validatedValue,
|
|
48
|
+
stockOnHand: state.stockOnHand?.validatedValue,
|
|
49
|
+
trackInventory: state.trackInventory?.validatedValue,
|
|
50
|
+
taxCategoryId: state.taxCategoryId?.validatedValue,
|
|
51
|
+
useGlobalOutOfStockThreshold: state.useGlobalOutOfStockThreshold?.validatedValue,
|
|
52
|
+
stockLevels: state.stockLevels?.validatedValue,
|
|
53
|
+
facetValueIds: state.facetValueIds?.validatedValue,
|
|
54
|
+
optionIds: state.optionIds?.validatedValue,
|
|
55
|
+
...(state.customFields?.validatedValue ? { customFields: state.customFields?.validatedValue } : {}),
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: true,
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
})
|
|
64
|
+
.then(() => {
|
|
65
|
+
toast(t('toasts.createProductVariantSuccessToast'));
|
|
66
|
+
onActionCompleted();
|
|
67
|
+
})
|
|
68
|
+
.catch(() => {
|
|
69
|
+
toast(t('toasts.createProductVariantErrorToast'));
|
|
70
|
+
});
|
|
71
|
+
}, [state, productId, onActionCompleted, t]);
|
|
72
|
+
const updateVariant = useCallback(() => {
|
|
73
|
+
if (!variant)
|
|
74
|
+
return;
|
|
75
|
+
apiClient('mutation')({
|
|
76
|
+
updateProductVariants: [
|
|
77
|
+
{
|
|
78
|
+
input: [
|
|
79
|
+
{
|
|
80
|
+
id: variant.id,
|
|
81
|
+
translations: state.translations?.validatedValue,
|
|
82
|
+
// price: +state.price?.validatedValue,
|
|
83
|
+
prices: state.prices?.validatedValue,
|
|
84
|
+
sku: state.sku?.validatedValue,
|
|
85
|
+
assetIds: state.assetIds?.validatedValue,
|
|
86
|
+
featuredAssetId: state.featuredAssetId?.validatedValue,
|
|
87
|
+
outOfStockThreshold: state.outOfStockThreshold?.validatedValue,
|
|
88
|
+
stockOnHand: state.stockOnHand?.validatedValue,
|
|
89
|
+
trackInventory: state.trackInventory?.validatedValue,
|
|
90
|
+
taxCategoryId: state.taxCategoryId?.validatedValue,
|
|
91
|
+
useGlobalOutOfStockThreshold: state.useGlobalOutOfStockThreshold?.validatedValue,
|
|
92
|
+
stockLevels: state.stockLevels?.validatedValue,
|
|
93
|
+
facetValueIds: state.facetValueIds?.validatedValue,
|
|
94
|
+
...(state.customFields?.validatedValue ? { customFields: state.customFields?.validatedValue } : {}),
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
{ id: true },
|
|
99
|
+
],
|
|
100
|
+
})
|
|
101
|
+
.then(() => {
|
|
102
|
+
onActionCompleted();
|
|
103
|
+
toast(t('toasts.updateProductSuccessToast'), {
|
|
104
|
+
description: new Date().toLocaleString(),
|
|
105
|
+
});
|
|
106
|
+
})
|
|
107
|
+
.catch(() => toast.error(t('toasts.updateProductErrorToast')));
|
|
108
|
+
}, [state, variant]);
|
|
109
|
+
const deleteVariant = useCallback(() => {
|
|
110
|
+
if (!variant)
|
|
111
|
+
return;
|
|
112
|
+
apiClient('mutation')({
|
|
113
|
+
deleteProductVariant: [{ id: variant.id }, { message: true }],
|
|
114
|
+
})
|
|
115
|
+
.then(() => {
|
|
116
|
+
onActionCompleted();
|
|
117
|
+
toast(t('toasts.deleteProductVariantSuccessToast'), {
|
|
118
|
+
description: new Date().toLocaleString(),
|
|
119
|
+
});
|
|
120
|
+
})
|
|
121
|
+
.catch(() => toast.error(t('toasts.deleteProductVariantErrorToast')));
|
|
122
|
+
}, [variant]);
|
|
123
|
+
const setTranslationField = useCallback((field, e) => {
|
|
124
|
+
setField('translations', setInArrayBy(translations, (t) => t.languageCode !== currentTranslationLng, {
|
|
125
|
+
[field]: e.target.value,
|
|
126
|
+
languageCode: currentTranslationLng,
|
|
127
|
+
}));
|
|
128
|
+
}, [currentTranslationLng, translations]);
|
|
129
|
+
const handleAddAsset = (id) => {
|
|
130
|
+
if (!id)
|
|
131
|
+
return;
|
|
132
|
+
const newIds = state.assetIds?.value || [];
|
|
133
|
+
if (newIds?.includes(id))
|
|
134
|
+
return;
|
|
135
|
+
newIds?.push(id);
|
|
136
|
+
setField('assetIds', newIds);
|
|
137
|
+
};
|
|
138
|
+
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: state.price?.value, taxRateValue: state.taxCategoryId?.value, outOfStockThresholdValue: state.outOfStockThreshold?.value, stockLevelsValue: state.stockLevels?.value, stockOnHandValue: state.stockOnHand?.value, useGlobalOutOfStockThresholdValue: state.useGlobalOutOfStockThreshold?.value, onThresholdChange: (e) => setField('outOfStockThreshold', +e.target.value), onUseGlobalChange: (e) => setField('useGlobalOutOfStockThreshold', e), onTrackInventoryChange: (e) => setField('trackInventory', e), onStockOnHandChange: (e) => setField('stockOnHand', +e.target.value), onStockLocationsChange: (e) => setField('stockLevels', e), allStockLocations: variant?.stockLevels, stockAllocated: variant?.stockAllocated, trackInventoryValue: state.trackInventory?.value })), _jsx(PriceCard, { currencyCode: variant?.currencyCode || CurrencyCode.PLN, priceValue: state.prices?.value, onPriceChange: (e) => setField('prices', e), taxRateValue: state.taxCategoryId?.value ?? undefined, onTaxRateChange: (id) => setField('taxCategoryId', id) }), _jsx(AssetsCard, { onAddAsset: handleAddAsset, featuredAssetId: state.featuredAssetId?.value, assetsIds: state.assetIds?.value, onFeaturedAssetChange: (id) => setField('featuredAssetId', id), onAssetsChange: (ids) => setField('assetIds', ids) }), _jsx(EntityCustomFields, { entityName: "productVariant", id: variant?.id, hideButton: true, onChange: (customFields, translations) => {
|
|
139
|
+
setField('customFields', customFields);
|
|
140
|
+
if (translations)
|
|
141
|
+
setField('translations', translations);
|
|
142
|
+
}, initialValues: variant && 'customFields' in variant
|
|
143
|
+
? { customFields: variant.customFields, translations: variant.translations }
|
|
144
|
+
: { 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: state?.sku?.value ?? undefined, onChange: (e) => setField('sku', e.target.value) }), _jsx(Input, { label: t('name'), placeholder: t('name'), value: currentTranslationValue?.name ?? undefined, onChange: (e) => setTranslationField('name', e) })] }) }), _jsx(EntityChannelManager, { entity: "productVariant", entityId: variant?.id, entityChannels: variant?.channels ?? [], onRemoveSuccess: onActionCompleted, entityName: variant?.name, entityVariantList: {
|
|
145
|
+
items: [
|
|
146
|
+
{ price: variant?.price, priceWithTax: variant?.priceWithTax, currencyCode: variant?.currencyCode },
|
|
147
|
+
],
|
|
148
|
+
} }), _jsx(OptionsCard, { optionGroups: variant?.options || [], productId: productId, optionIds: state.optionIds?.value ?? undefined, onChange: (e) => setField('optionIds', e), createMode: !variant }), !!variant && (_jsx(FacetValuesCard, { facetValuesIds: state.facetValueIds?.value ?? undefined, onChange: (e) => setField('facetValueIds', e) }))] })] })] }));
|
|
149
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { apiClient, EmptyState, Tabs, TabsContent, TabsList, TabsTrigger, useDetailView, useSettings, useTranslation, } from '@deenruv/react-ui-devkit';
|
|
3
|
+
import { ProductVariantSelector } from "../../../graphql/products";
|
|
4
|
+
import { Variant } from "./Variant";
|
|
5
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
6
|
+
import { PlusCircle } from 'lucide-react';
|
|
7
|
+
import { useSearchParams } from 'react-router-dom';
|
|
8
|
+
const NEW_VARIANT_TAB_VALUE = 'new';
|
|
9
|
+
export const VariantsTab = () => {
|
|
10
|
+
const contentLanguage = useSettings((p) => p.translationsLanguage);
|
|
11
|
+
const { id, getMarker, setLoading } = useDetailView('products-detail-view', 'CreateProductInput');
|
|
12
|
+
const [activeTab, setActiveTab] = useState(undefined);
|
|
13
|
+
const { t } = useTranslation('products');
|
|
14
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
15
|
+
const handleTabChange = (tab) => {
|
|
16
|
+
setActiveTab(tab);
|
|
17
|
+
const updatedParams = new URLSearchParams(searchParams);
|
|
18
|
+
updatedParams.set('variantId', tab);
|
|
19
|
+
setSearchParams(updatedParams);
|
|
20
|
+
};
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const variantId = searchParams.get('variantId');
|
|
23
|
+
if (variantId)
|
|
24
|
+
setActiveTab(variantId);
|
|
25
|
+
}, []);
|
|
26
|
+
const [variants, setVariants] = useState();
|
|
27
|
+
const fetchData = useCallback(async () => {
|
|
28
|
+
if (id) {
|
|
29
|
+
setLoading(true);
|
|
30
|
+
const response = await apiClient('query')({
|
|
31
|
+
productVariants: [{ productId: id }, { items: ProductVariantSelector }],
|
|
32
|
+
});
|
|
33
|
+
setLoading(false);
|
|
34
|
+
if (response.productVariants) {
|
|
35
|
+
setVariants(response.productVariants.items);
|
|
36
|
+
if (activeTab === undefined) {
|
|
37
|
+
setActiveTab(response.productVariants.items[0]?.id);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}, [id, activeTab]);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
fetchData();
|
|
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') })) }))] }) })] }));
|
|
46
|
+
};
|