From ebb831464339bd28c446e9ac56d2414d20e2445a Mon Sep 17 00:00:00 2001 From: nguyenvanhadncntt Date: Tue, 29 Oct 2024 16:08:33 +0700 Subject: [PATCH] #1206 product: Allow admin to add multiple values for option --- .../catalog/components/ProductVariation.tsx | 76 ++++++++++++++++--- .../modules/catalog/models/ProductPayload.ts | 2 +- backoffice/pages/catalog/products/create.tsx | 4 +- backoffice/styles/globals.css | 22 ++++++ .../controller/ProductControllerIT.java | 6 +- .../product/model/ProductVariationSaveVm.java | 2 +- .../yas/product/service/ProductService.java | 31 ++++---- .../product/ProductVariationPostVm.java | 2 +- .../product/ProductVariationPutVm.java | 2 +- 9 files changed, 116 insertions(+), 31 deletions(-) diff --git a/backoffice/modules/catalog/components/ProductVariation.tsx b/backoffice/modules/catalog/components/ProductVariation.tsx index ae60e9b3b9..3e5147a7a1 100644 --- a/backoffice/modules/catalog/components/ProductVariation.tsx +++ b/backoffice/modules/catalog/components/ProductVariation.tsx @@ -24,6 +24,7 @@ const ProductVariations = ({ getValue, setValue }: Props) => { const [productOptions, setProductOptions] = useState([]); const [selectedOptions, setSelectedOptions] = useState([]); const [optionCombines, setOptionCombines] = useState([]); + const [optionValueArray, setOptionValueArray] = useState({}); useEffect(() => { if (id) { @@ -80,6 +81,9 @@ const ProductVariations = ({ getValue, setValue }: Props) => { const index = selectedOptions.indexOf(currentOption.name); if (index === -1) { setSelectedOptions([...selectedOptions, currentOption.name]); + if (!optionValueArray[currentOption.name]) { + setOptionValueArray({ ...optionValueArray, ...{ [currentOption.name]: 1 } }); + } } else { toast.info(`${currentOption.name} is selected. Select Other`); } @@ -115,22 +119,35 @@ const ProductVariations = ({ getValue, setValue }: Props) => { return toast.warning('Combined Option Values are Duplicated'); } + let optionValuesByOptionIds = {}; + + optionValuesByOptionId.forEach((value, key, fooMap) => { + optionValuesByOptionIds = Object.assign(optionValuesByOptionIds, { [key]: value }); + }); + const newVariation: ProductVariation = { optionName: variationName, optionGTin: getValue('gtin') ?? '', optionSku: getValue('sku') ?? '', optionPrice: getValue('price') ?? 0, - optionValuesByOptionId: Object.fromEntries(optionValuesByOptionId), + optionValuesByOptionId: optionValuesByOptionIds, }; setOptionCombines([variationName]); setValue('productVariations', [...formProductVariations, newVariation]); }; - const generateProductOptionCombinations = (): Map => { - const optionValuesByOptionId = new Map(); + const generateProductOptionCombinations = (): Map => { + const optionValuesByOptionId = new Map(); let isEmptyOptions = false; + const optionValues = [] as string[]; selectedOptions.forEach((option) => { if (isEmptyOptions) return; + document.getElementsByName(option).forEach((element) => { + const value = (element as HTMLInputElement).value; + if (value !== '') { + optionValues.push(value); + } + }); const optionValue = (document.getElementById(option) as HTMLInputElement).value; if (optionValue === '') { isEmptyOptions = true; @@ -138,9 +155,9 @@ const ProductVariations = ({ getValue, setValue }: Props) => { } const productOption = productOptions.find((productOption) => productOption.name === option); const productOptionId = productOption?.id ?? -1; - optionValuesByOptionId.set(productOptionId, optionValue); + optionValuesByOptionId.set(productOptionId, optionValues); }); - return isEmptyOptions ? new Map() : optionValuesByOptionId; + return isEmptyOptions ? new Map() : optionValuesByOptionId; }; const onDeleteVariation = (variant: ProductVariation) => { @@ -152,6 +169,17 @@ const ProductVariations = ({ getValue, setValue }: Props) => { setValue('productVariations', productVar); }; + const addOptionValue = (option: string) => { + setOptionValueArray({ ...optionValueArray, ...{ [option]: optionValueArray[option] + 1 } }); + }; + + const removeOptionValue = (option: string) => { + setOptionValueArray({ ...optionValueArray, ...{ [option]: optionValueArray[option] - 1 } }); + if (optionValueArray[option] === 0) { + removeOptionValue(option); + } + }; + return ( <> {/* Selection */} @@ -184,17 +212,45 @@ const ProductVariations = ({ getValue, setValue }: Props) => {
Value Options
{(selectedOptions || []).map((option) => ( -
+
- -
+
))}
diff --git a/backoffice/modules/catalog/models/ProductPayload.ts b/backoffice/modules/catalog/models/ProductPayload.ts index 5cb1dbe4f8..4719ae4d74 100644 --- a/backoffice/modules/catalog/models/ProductPayload.ts +++ b/backoffice/modules/catalog/models/ProductPayload.ts @@ -106,7 +106,7 @@ const createProductOptionValues = (productVariations: ProductVariation[]) => { productOptionValues.push({ productOptionId: id, displayOrder: 1, - value: [value], + value: Array.isArray(value) ? value : [value], }); } }); diff --git a/backoffice/pages/catalog/products/create.tsx b/backoffice/pages/catalog/products/create.tsx index a3cb30f361..841efb9eb3 100644 --- a/backoffice/pages/catalog/products/create.tsx +++ b/backoffice/pages/catalog/products/create.tsx @@ -1,5 +1,7 @@ import { NextPage } from 'next'; import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { useEffect, useState } from 'react'; import { Tab, Tabs } from 'react-bootstrap'; import { SubmitHandler, useForm } from 'react-hook-form'; import { toast } from 'react-toastify'; @@ -20,8 +22,6 @@ import { ProductAttributeValuePost } from '../../../modules/catalog/models/Produ import { mapFormProductToProductPayload } from '../../../modules/catalog/models/ProductPayload'; import { createProductAttributeValueOfProduct } from '../../../modules/catalog/services/ProductAttributeValueService'; import { createProduct } from '../../../modules/catalog/services/ProductService'; -import { useRouter } from 'next/router'; -import { useEffect, useState } from 'react'; const ProductCreate: NextPage = () => { const router = useRouter(); diff --git a/backoffice/styles/globals.css b/backoffice/styles/globals.css index dd20566a81..8cd9fb01a0 100644 --- a/backoffice/styles/globals.css +++ b/backoffice/styles/globals.css @@ -293,3 +293,25 @@ a[data-toggle='collapse'] { margin-left: 5px; cursor: pointer; } + +.option-value-box { + position: relative; + background-color: #fff; + border: 2px solid #ccc; + padding: 20px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + border-radius: 8px; +} + +.close { + position: absolute; + top: 10px; + right: 10px; + font-size: 20px; + cursor: pointer; + color: #333; +} + +.close:hover { + color: #ff0000; +} diff --git a/product/src/it/java/com/yas/product/controller/ProductControllerIT.java b/product/src/it/java/com/yas/product/controller/ProductControllerIT.java index 70c96f2428..ca208693f7 100644 --- a/product/src/it/java/com/yas/product/controller/ProductControllerIT.java +++ b/product/src/it/java/com/yas/product/controller/ProductControllerIT.java @@ -163,10 +163,12 @@ void initProductPutVm() { ProductVariationPutVm productVariationPutVm1 = new ProductVariationPutVm( 1L, "Sample Product Variation 2", "sample-product-variation-1", "SKU1", - "GTIN1", 19.99, 101L, new ArrayList<>(), new HashMap<>(Map.of(productOption.getId(), "product-option-value-1"))); + "GTIN1", 19.99, 101L, new ArrayList<>(), + new HashMap<>(Map.of(productOption.getId(), List.of("product-option-value-1")))); ProductVariationPutVm productVariationPutVm2 = new ProductVariationPutVm( null, "Sample Product Variation 2", "sample-product-variation-2", "SKU2", - "GTIN2", 29.99, 102L, new ArrayList<>(), new HashMap<>(Map.of(productOption.getId(), "product-option-value-2"))); + "GTIN2", 29.99, 102L, new ArrayList<>(), + new HashMap<>(Map.of(productOption.getId(), List.of("product-option-value-2")))); ProductOptionValuePutVm productOptionValuePutVm = new ProductOptionValuePutVm( productOption.getId(), "Visible", diff --git a/product/src/main/java/com/yas/product/model/ProductVariationSaveVm.java b/product/src/main/java/com/yas/product/model/ProductVariationSaveVm.java index e09b82afbf..ce4f74a213 100644 --- a/product/src/main/java/com/yas/product/model/ProductVariationSaveVm.java +++ b/product/src/main/java/com/yas/product/model/ProductVariationSaveVm.java @@ -11,5 +11,5 @@ public interface ProductVariationSaveVm extends ProductProperties { List productImageIds(); - Map optionValuesByOptionId(); + Map> optionValuesByOptionId(); } diff --git a/product/src/main/java/com/yas/product/service/ProductService.java b/product/src/main/java/com/yas/product/service/ProductService.java index b6606fa626..bed9433f3e 100644 --- a/product/src/main/java/com/yas/product/service/ProductService.java +++ b/product/src/main/java/com/yas/product/service/ProductService.java @@ -328,20 +328,25 @@ private void createOptionCombinations( variationVm.optionValuesByOptionId().forEach((optionId, optionValue) -> { ProductOption productOption = optionsById.get(optionId); - ProductOptionValue foundOptionValue = optionValues.stream() + List foundOptionValues = optionValues.stream() .filter( - pov -> pov.getProductOption().getId().equals(optionId) && pov.getValue().equals(optionValue)) - .findFirst() - .orElseThrow(() - -> new BadRequestException(Constants.ErrorCode.PRODUCT_OPTION_VALUE_IS_NOT_FOUND, optionValue)); - - ProductOptionCombination optionCombination = ProductOptionCombination.builder() - .product(savedVariation) - .productOption(productOption) - .value(foundOptionValue.getValue()) - .displayOrder(foundOptionValue.getDisplayOrder()) - .build(); - optionCombinations.add(optionCombination); + pov -> pov.getProductOption().getId().equals(optionId) && optionValue.contains(pov.getValue())) + .toList(); + + if (CollectionUtils.isEmpty(foundOptionValues)) { + throw new BadRequestException(Constants.ErrorCode.PRODUCT_OPTION_VALUE_IS_NOT_FOUND, optionValue); + } + + List optionCombinationEntities = foundOptionValues.stream() + .map(foundOptionValue -> + ProductOptionCombination.builder() + .product(savedVariation) + .productOption(productOption) + .value(foundOptionValue.getValue()) + .displayOrder(foundOptionValue.getDisplayOrder()) + .build() + ).toList(); + optionCombinations.addAll(optionCombinationEntities); }); } productOptionCombinationRepository.saveAll(optionCombinations); diff --git a/product/src/main/java/com/yas/product/viewmodel/product/ProductVariationPostVm.java b/product/src/main/java/com/yas/product/viewmodel/product/ProductVariationPostVm.java index 9ce24ab28b..4740836eab 100644 --- a/product/src/main/java/com/yas/product/viewmodel/product/ProductVariationPostVm.java +++ b/product/src/main/java/com/yas/product/viewmodel/product/ProductVariationPostVm.java @@ -12,7 +12,7 @@ public record ProductVariationPostVm( Double price, Long thumbnailMediaId, List productImageIds, - Map optionValuesByOptionId + Map> optionValuesByOptionId ) implements ProductVariationSaveVm { @Override diff --git a/product/src/main/java/com/yas/product/viewmodel/product/ProductVariationPutVm.java b/product/src/main/java/com/yas/product/viewmodel/product/ProductVariationPutVm.java index 60249a73be..7ed1d62070 100644 --- a/product/src/main/java/com/yas/product/viewmodel/product/ProductVariationPutVm.java +++ b/product/src/main/java/com/yas/product/viewmodel/product/ProductVariationPutVm.java @@ -13,6 +13,6 @@ public record ProductVariationPutVm( Double price, Long thumbnailMediaId, List productImageIds, - Map optionValuesByOptionId + Map> optionValuesByOptionId ) implements ProductVariationSaveVm { }