From 589a7a6897270f035f21abbf560e99b457d0a6a9 Mon Sep 17 00:00:00 2001 From: nguyenvanhadncntt <35553635+nguyenvanhadncntt@users.noreply.github.com> Date: Thu, 19 Sep 2024 10:49:02 +0700 Subject: [PATCH] 1034 implement create promotion page (#1050) --- backoffice/common/items/Input.tsx | 85 ++++- .../components/MultipleAutoComplete.tsx | 110 ++++++ .../PromotionGeneralInformation.tsx | 319 ++++++++++++++++++ .../modules/promotion/models/Promotion.ts | 24 +- .../promotion/services/ProductService.ts | 20 ++ .../promotion/services/PromotionService.ts | 17 +- .../promotion/manager-promotion/create.tsx | 86 +++++ .../promotion/manager-promotion/index.tsx | 4 +- backoffice/styles/globals.css | 38 +++ .../product/controller/BrandController.java | 3 +- .../controller/CategoryController.java | 3 +- .../promotion/service/PromotionServiceIT.java | 15 +- .../controller/PromotionController.java | 16 +- .../repository/PromotionRepository.java | 4 +- .../promotion/service/PromotionService.java | 20 +- .../yas/promotion/viewmodel/PromotionDto.java | 2 + .../promotion/viewmodel/PromotionPostVm.java | 12 +- .../promotion/viewmodel/PromotionPutVm.java | 5 +- .../controller/PromotionControllerTest.java | 66 ++-- .../service/PromotionServiceTest.java | 161 +++++++++ 20 files changed, 942 insertions(+), 68 deletions(-) create mode 100644 backoffice/modules/promotion/components/MultipleAutoComplete.tsx create mode 100644 backoffice/modules/promotion/components/PromotionGeneralInformation.tsx create mode 100644 backoffice/modules/promotion/services/ProductService.ts create mode 100644 backoffice/pages/promotion/manager-promotion/create.tsx create mode 100644 promotion/src/test/java/com/yas/promotion/service/PromotionServiceTest.java diff --git a/backoffice/common/items/Input.tsx b/backoffice/common/items/Input.tsx index 69cf4268ca..8e15f77c04 100644 --- a/backoffice/common/items/Input.tsx +++ b/backoffice/common/items/Input.tsx @@ -1,5 +1,5 @@ import { HTMLInputTypeAttribute } from 'react'; -import { Path, RegisterOptions, UseFormRegister, FieldValues } from 'react-hook-form'; +import { FieldValues, Path, RegisterOptions, UseFormRegister } from 'react-hook-form'; type InputProps = { labelText: string; @@ -10,12 +10,30 @@ type InputProps = { registerOptions?: RegisterOptions; defaultValue?: number | string | string[]; disabled?: boolean; + onChange?: (event: React.ChangeEvent) => void; }; type CheckProps = InputProps & { defaultChecked?: any; }; +type SelectProps = InputProps & { + options: any[]; + placeholder?: string; + defaultValue?: number | string | string[]; + disabled?: boolean; + isMultiple?: boolean; + onChange?: (event: React.ChangeEvent) => void; +}; + +type DateProps = InputProps & { + minDate?: Date; + maxDate?: Date; + defaultValue?: string; + disabled?: boolean; + placeholder?: string; +}; + export const Input = ({ labelText, field, @@ -111,3 +129,68 @@ export const Switch = ({ ); + +export const Select = ({ + labelText, + field, + register, + registerOptions, + error, + options, + defaultValue, + placeholder, + disabled, + isMultiple, + onChange, +}: SelectProps & { + options: any[]; +}) => ( +
+ + +

{error}

+
+); + +export const DatePicker = ({ + labelText, + field, + register, + registerOptions = {}, + error, + defaultValue, +}: DateProps) => ( +
+ + +

{error}

+
+); diff --git a/backoffice/modules/promotion/components/MultipleAutoComplete.tsx b/backoffice/modules/promotion/components/MultipleAutoComplete.tsx new file mode 100644 index 0000000000..a7e327e004 --- /dev/null +++ b/backoffice/modules/promotion/components/MultipleAutoComplete.tsx @@ -0,0 +1,110 @@ +import { useState } from 'react'; +import { RegisterOptions, UseFormRegister } from 'react-hook-form'; + +type props = { + labelText: string; + field: string; + register: UseFormRegister; + registerOptions?: RegisterOptions; + // error: string, + defaultValue?: any; + options?: any[]; + fetchOptions: (data: any) => any; + onSelect: (value: any) => void; + onRemoveElement: (value: any) => void; + optionSelectedIds: number[]; + isSubmitting: boolean; +}; + +const MultipleAutoComplete = (props: props) => { + const [isFocusing, setIsFocusing] = useState(false); + const [optionSelecteds, setOptionSelecteds] = useState([]); + const queryData = (query: string) => { + props.fetchOptions(query); + }; + + const handleFocus = (isFocusing: boolean) => { + setTimeout(() => { + if (!props.isSubmitting) { + setIsFocusing(isFocusing); + } else { + setIsFocusing(false); + } + }, 150); + }; + + const selectOption = (option: any) => { + setOptionSelecteds([...optionSelecteds, option]); + props.onSelect(option.id); + }; + + const removeOption = (option: any) => { + setOptionSelecteds(optionSelecteds.filter((item) => item.id !== option.id)); + props.onRemoveElement(option.id); + }; + + return ( +
+ +
+ queryData(e.target.value)} + onFocus={() => handleFocus(true)} + onBlur={() => handleFocus(false)} + className="form-control" + /> + {isFocusing && props.options!.length > 0 && ( +
+ {props.options!.map((option, index) => ( + + ))} +
+ )} +
+ + {optionSelecteds.length > 0 && ( +
+ Selected {props.labelText} + {optionSelecteds.map((option, index) => ( +
+
+ {option.name} +
+ +
+ ))} +
+ )} +
+ ); +}; + +export default MultipleAutoComplete; diff --git a/backoffice/modules/promotion/components/PromotionGeneralInformation.tsx b/backoffice/modules/promotion/components/PromotionGeneralInformation.tsx new file mode 100644 index 0000000000..d46807dea5 --- /dev/null +++ b/backoffice/modules/promotion/components/PromotionGeneralInformation.tsx @@ -0,0 +1,319 @@ +import { useEffect, useState } from 'react'; +import { FieldErrorsImpl, UseFormRegister, UseFormSetValue, UseFormTrigger } from 'react-hook-form'; +import { DatePicker, Input, Select, Switch, TextArea } from '../../../common/items/Input'; +import { PromotionDetail, PromotionDto } from '../models/Promotion'; +import { searchBrands, searchCategories, searchProducts } from '../services/ProductService'; +import MultipleAutoComplete from './MultipleAutoComplete'; + +type Props = { + register: UseFormRegister; + errors: FieldErrorsImpl; + setValue: UseFormSetValue; + trigger: UseFormTrigger; + promotion?: PromotionDetail; + isSubmitting: boolean; +}; + +const PromotionGeneralInformation = ({ + register, + errors, + setValue, + trigger, + promotion, + isSubmitting, +}: Props) => { + const [discountType, setDiscountType] = useState(promotion?.discountType); + const [usageType, setUsageType] = useState(promotion?.usageType); + const [applyTo, setApplyTo] = useState(promotion?.applyTo); + const [brands, setBrands] = useState(promotion?.brands.map((brand) => brand.id) ?? []); + const [categories, setCategories] = useState( + promotion?.categories.map((category) => category.id) ?? [] + ); + const [products, setProducts] = useState(promotion?.products.map((product) => product.id) ?? []); + const [productVms, setProductVms] = useState(promotion?.products ?? []); + const [brandVms, setBrandVms] = useState(promotion?.brands ?? []); + const [categoryVms, setCategoryVms] = useState(promotion?.categories ?? []); + + useEffect(() => { + if (applyTo === 'PRODUCT') { + searchProducts('').then((data) => { + setProductVms(data.productContent); + }); + } + }, [applyTo]); + + const convertDateToString = (date?: Date | string) => { + if (date) { + if (typeof date === 'string') { + return date; + } + const month = date.getMonth() + 1; + return `${date.getFullYear()}-${month > 9 ? month : '0' + month}-${date.getDate()}`; + } + return ''; + }; + + const onSelectProducts = (id: number) => { + setValue('productIds', [...(products ?? []), id]); + setProducts([...(products ?? []), id]); + }; + + const removeProduct = (id: number) => { + const productFilters = products?.filter((product) => product !== id); + setProducts(productFilters); + setValue('productIds', productFilters); + }; + + const fetchProducts = (name: string) => { + searchProducts(name).then((data) => { + setProductVms(data.productContent); + }); + }; + + const fetchCategories = (name: string) => { + searchCategories(name).then((data) => { + setCategoryVms(data); + }); + }; + + const removeCategory = (id: number) => { + const categoryFilters = categories?.filter((category) => category !== id); + setCategories(categoryFilters); + setValue('categoryIds', categoryFilters); + }; + + const onSelectCategory = (id: number) => { + setValue('categoryIds', [...(categories ?? []), id]); + setCategories([...(categories ?? []), id]); + }; + + const fetchBrands = (name: string) => { + searchBrands(name).then((data) => { + setBrandVms(data); + }); + }; + + const removeBrand = (id: number) => { + const brandFilters = brands?.filter((brand) => brand !== id); + setBrands(brandFilters); + setValue('brandIds', brandFilters); + }; + + const onSelectBrand = (id: number) => { + setValue('brandIds', [...(brands ?? []), id]); + setBrands([...(brands ?? []), id]); + }; + + return ( + <> + + + +