From ff7d104e981c38351b06f0aa91ec2734d6016900 Mon Sep 17 00:00:00 2001 From: oak Date: Fri, 6 Sep 2024 16:19:14 +0300 Subject: [PATCH 1/5] wip --- .../genericProductComponent.tsx | 28 +++++++++++++++---- .../interface/interface.ts | 3 +- .../mediaView/mediaView.tsx | 4 +-- .../genericProductComponent/tags/tags.tsx | 9 +++--- .../products/listProducts/listProducts.tsx | 23 +++++++-------- .../products/productForm/productForm.tsx | 2 +- src/styles/addProd.scss | 3 +- src/styles/paged.scss | 2 +- 8 files changed, 48 insertions(+), 26 deletions(-) diff --git a/src/components/managers/products/genericProductComponent/genericProductComponent.tsx b/src/components/managers/products/genericProductComponent/genericProductComponent.tsx index e82401ee..19a595b2 100644 --- a/src/components/managers/products/genericProductComponent/genericProductComponent.tsx +++ b/src/components/managers/products/genericProductComponent/genericProductComponent.tsx @@ -56,7 +56,6 @@ export const GenericProductForm: FC = ({ setClearMediaPreview(true); setTimeout(() => setClearMediaPreview(false), 0); } - if (onEditModeChange) onEditModeChange(false); }; const checkChanges = useCallback( @@ -72,7 +71,7 @@ export const GenericProductForm: FC = ({ enableReinitialize validationSchema={validationSchema} > - {({ isSubmitting, values }) => { + {({ handleSubmit, isSubmitting, values }) => { useEffect(() => checkChanges(values), [checkChanges, values]); return ( @@ -85,7 +84,16 @@ export const GenericProductForm: FC = ({ { const isCopyMode = match.pathname.includes('/copy'); const [product, setProduct] = useState(); const [dictionary, setDictionary] = useState(); - const [isEditMode, setIsEditMode] = useState(!!id && !isCopyMode); + const [isEditMode, setIsEditMode] = useState(false); const [initialValues, setInitialValues] = useState(productInitialValues()); const [snackBarMessage, setSnackBarMessage] = useState(''); const [isSnackBarOpen, setIsSnackBarOpen] = useState(false); diff --git a/src/styles/addProd.scss b/src/styles/addProd.scss index 005abce9..680bc57e 100644 --- a/src/styles/addProd.scss +++ b/src/styles/addProd.scss @@ -14,9 +14,10 @@ .delete_btn { position: absolute; - top: 10px; + top: 15px; right: 0; display: none; + border-radius: 0%; } &:hover { diff --git a/src/styles/paged.scss b/src/styles/paged.scss index 942b439c..f5dec366 100644 --- a/src/styles/paged.scss +++ b/src/styles/paged.scss @@ -26,7 +26,7 @@ .copy_btn { position: absolute; - top: 0; + bottom: 0; left: 0; padding: 5px; border-radius: 0%; From 925e0db74e2feb66943bf2671cfe05ae55c5da9e Mon Sep 17 00:00:00 2001 From: oak Date: Mon, 9 Sep 2024 16:35:21 +0300 Subject: [PATCH 2/5] tag is fixed. in future need to add pagination for tags from localStorage --- .../basicFields/basicFields.tsx | 124 +++++++++++------- .../genericProductComponent.tsx | 22 +++- .../genericProductComponent/tags/tags.tsx | 22 +++- .../utility/formilValidationShema.ts | 55 ++++---- .../utility/productInitialValues.ts | 6 +- 5 files changed, 141 insertions(+), 88 deletions(-) diff --git a/src/components/managers/products/genericProductComponent/basicFields/basicFields.tsx b/src/components/managers/products/genericProductComponent/basicFields/basicFields.tsx index c06cd96b..e4aef582 100644 --- a/src/components/managers/products/genericProductComponent/basicFields/basicFields.tsx +++ b/src/components/managers/products/genericProductComponent/basicFields/basicFields.tsx @@ -18,7 +18,7 @@ import { isValid, parseISO } from 'date-fns'; import { generateOrUpdateSKU, generateSKU } from 'features/utilitty/dynamicGenerationOfSku'; import { findInDictionary } from 'features/utilitty/findInDictionary'; import { restrictNumericInput } from 'features/utilitty/removePossibilityToEnterSigns'; -import { ErrorMessage, Field, useFormikContext } from 'formik'; +import { ErrorMessage, Field, getIn, useFormikContext } from 'formik'; import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; import CountryList from 'react-select-country-list'; import { v4 as uuidv4 } from 'uuid'; @@ -183,9 +183,15 @@ export const BasicFields: FC = ({ name='product.productBody.name' required fullWidth - error={!!(errors.product && touched.product && !values.product?.productBody?.name)} - helperText={} InputLabelProps={{ shrink: true }} + error={Boolean( + getIn(errors, 'product.productBody.name') && + getIn(touched, 'product.productBody.name'), + )} + helperText={ + getIn(touched, 'product.productBody.name') && + getIn(errors, 'product.productBody.name') + } disabled={disableFields} onKeyDown={handleKeyDown} /> @@ -198,9 +204,15 @@ export const BasicFields: FC = ({ name='product.productBody.brand' required fullWidth - error={!!(errors.product && touched.product && !values.product?.productBody?.brand)} - helperText={} InputLabelProps={{ shrink: true }} + error={Boolean( + getIn(errors, 'product.productBody.brand') && + getIn(touched, 'product.productBody.brand'), + )} + helperText={ + getIn(touched, 'product.productBody.brand') && + getIn(errors, 'product.productBody.brand') + } onChange={(e: React.ChangeEvent) => handleFieldChange(e, 'brand')} onKeyDown={handleKeyDown} disabled={disableFields} @@ -210,9 +222,10 @@ export const BasicFields: FC = ({ {'gender'.toUpperCase()} - {!values.product?.productBody?.targetGender && submitCount > 0 && ( - - - - )} + {getIn(touched, 'product.productBody.targetGender') && + getIn(errors, 'product.productBody.targetGender') && ( + + + + )} {'category'.toUpperCase()} - {!values.product?.productBody?.categoryId && submitCount > 0 && ( - - - - )} + {getIn(touched, 'product.productBody.categoryId') && + getIn(errors, 'product.productBody.categoryId') && ( + + + + )} {'color'.toUpperCase()} - {!values.product?.productBody?.color && submitCount > 0 && ( - - - - )} + {getIn(touched, 'product.productBody.color') && + getIn(errors, 'product.productBody.color') && ( + + + + )} @@ -310,16 +330,17 @@ export const BasicFields: FC = ({ - {'color'.toUpperCase()} + {'country'.toUpperCase()} - {!values.product?.productBody?.countryOfOrigin && submitCount > 0 && ( - - - - )} + {getIn(touched, 'product.productBody.countryOfOrigin') && + getIn(errors, 'product.productBody.countryOfOrigin') && ( + + + + )} @@ -346,6 +368,14 @@ export const BasicFields: FC = ({ inputProps={{ min: 0, step: '0.01' }} required fullWidth + error={Boolean( + getIn(errors, 'product.productBody.price.value') && + getIn(touched, 'product.productBody.price.value'), + )} + helperText={ + getIn(touched, 'product.productBody.price.value') && + getIn(errors, 'product.productBody.price.value') + } InputLabelProps={{ shrink: true }} onChange={handlePriceChange} onKeyDown={restrictNumericInput} @@ -354,14 +384,6 @@ export const BasicFields: FC = ({ setFieldValue('product.productBody.price.value', formattedValue); }} disabled={disableFields} - error={ - !!( - errors.product && - touched.product && - values.product?.productBody?.price?.value === '0' - ) - } - helperText={} /> @@ -405,15 +427,19 @@ export const BasicFields: FC = ({ as={TextField} label={'description'.toUpperCase()} name='product.productBody.description' + error={Boolean( + getIn(errors, 'product.productBody.description') && + getIn(touched, 'product.productBody.description'), + )} + helperText={ + getIn(touched, 'product.productBody.description') && + getIn(errors, 'product.productBody.description') + } InputLabelProps={{ shrink: true }} fullWidth multiline required disabled={disableFields} - error={ - !!(errors.product && touched.product && !values.product?.productBody?.description) - } - helperText={} /> diff --git a/src/components/managers/products/genericProductComponent/genericProductComponent.tsx b/src/components/managers/products/genericProductComponent/genericProductComponent.tsx index 19a595b2..283c4d00 100644 --- a/src/components/managers/products/genericProductComponent/genericProductComponent.tsx +++ b/src/components/managers/products/genericProductComponent/genericProductComponent.tsx @@ -1,5 +1,7 @@ import { AppBar, Button, CircularProgress, Grid, Toolbar } from '@mui/material'; +import { useNavigate } from '@tanstack/react-location'; import { common_ProductNew, common_SizeWithMeasurementInsert } from 'api/proto-http/admin'; +import { ROUTES } from 'constants/routes'; import { Field, Form, Formik, FormikHelpers } from 'formik'; import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { BasicFields } from './basicFields/basicFields'; @@ -22,6 +24,7 @@ export const GenericProductForm: FC = ({ }) => { const [isFormChanged, setIsFormChanged] = useState(false); const [clearMediaPreview, setClearMediaPreview] = useState(false); + const navigate = useNavigate(); const initialValues = useMemo(() => initialProductState, [initialProductState]); useEffect(() => { @@ -64,11 +67,15 @@ export const GenericProductForm: FC = ({ [initialValues], ); + const handleCopyProductClick = (id: number | undefined) => { + navigate({ to: `${ROUTES.copyProduct}/${id}` }); + }; + return ( {({ handleSubmit, isSubmitting, values }) => { @@ -80,18 +87,23 @@ export const GenericProductForm: FC = ({ position='fixed' sx={{ top: 'auto', bottom: 0, backgroundColor: 'transparent', boxShadow: 'none' }} > - + + diff --git a/src/components/managers/products/genericProductComponent/utility/formilValidationShema.ts b/src/components/managers/products/genericProductComponent/utility/formilValidationShema.ts index 85af601e..742ba720 100644 --- a/src/components/managers/products/genericProductComponent/utility/formilValidationShema.ts +++ b/src/components/managers/products/genericProductComponent/utility/formilValidationShema.ts @@ -1,36 +1,39 @@ import * as Yup from 'yup'; export const validationSchema = Yup.object().shape({ - product: Yup.object() - .shape({ - productBody: Yup.object() - .shape({ - name: Yup.string() - .required('Name is required') - .matches(/^(?![_\.\-]+$)/, 'Name cannot consist only of special symbols'), - brand: Yup.string() - .required('Brand is required') - .matches(/^(?![_\.\-]+$)/, 'Brand cannot consist only of special symbols'), - targetGender: Yup.string().required('Gender is required'), - categoryId: Yup.number().required('Category is required'), - color: Yup.string().required('Color is required'), - countryOfOrigin: Yup.string().required('Country is required'), - price: Yup.object({ - value: Yup.number() - .min(0, 'Price must be greater than or equal to 0') - .required('Price is required'), - }).required(), - description: Yup.string().required('Description is required'), - }) - .required(), - thumbnailMediaId: Yup.number().required('Thumbnail must be selected'), - }) - .required(), + product: Yup.object().shape({ + productBody: Yup.object().shape({ + name: Yup.string() + .required('Name is required') + .matches(/^(?![_\.\-]+$)/, 'Name cannot consist only of special symbols'), + brand: Yup.string() + .required('Brand is required') + .matches(/^(?![_\.\-]+$)/, 'Brand cannot consist only of special symbols'), + targetGender: Yup.string().required('Gender is required'), + categoryId: Yup.number().required('Category is required'), + color: Yup.string().required('Color is required'), + countryOfOrigin: Yup.string().required('Country is required'), + price: Yup.object().shape({ + value: Yup.string() + .test( + 'is-valid-number', + 'Price must be a valid number greater than 0', + (value) => { + const parsedValue = parseFloat(value ?? ''); + return !isNaN(parsedValue) && parsedValue > 0; + } + ) + .required('Price is required'), + }).required(), + description: Yup.string().required('Description is required'), + }).required(), + thumbnailMediaId: Yup.number().required('Thumbnail must be selected'), + }).required(), mediaIds: Yup.array().min(2, 'At least two media must be added to the product'), sizeMeasurements: Yup.array().test( 'at-least-one-size', 'At least one size must be specified', - (sizes) => sizes?.some((size) => size.productSize?.quantity?.value !== '0'), + (sizes) => sizes?.some((size) => size.productSize?.quantity?.value !== '0') ), tags: Yup.array().min(1, 'At least one tag must be added to the product'), }); diff --git a/src/components/managers/products/genericProductComponent/utility/productInitialValues.ts b/src/components/managers/products/genericProductComponent/utility/productInitialValues.ts index 03571c7c..e3733322 100644 --- a/src/components/managers/products/genericProductComponent/utility/productInitialValues.ts +++ b/src/components/managers/products/genericProductComponent/utility/productInitialValues.ts @@ -1,4 +1,4 @@ -import { common_ProductFull, common_ProductNew } from "api/proto-http/admin"; +import { common_GenderEnum, common_ProductFull, common_ProductNew } from "api/proto-http/admin"; export const productInitialValues = (product?: common_ProductFull): common_ProductNew => { if (!product) { @@ -44,10 +44,10 @@ export const initialProductState: common_ProductNew = { countryOfOrigin: '', price: { value: '0' }, salePercentage: { value: '0' }, - categoryId: 0, + categoryId: undefined, description: '', hidden: false, - targetGender: 'GENDER_ENUM_UNKNOWN', + targetGender: '' as common_GenderEnum, }, thumbnailMediaId: undefined, }, From ffdde7a9b222555aaa76ff000f2dff5f92ed0dff Mon Sep 17 00:00:00 2001 From: oak Date: Mon, 9 Sep 2024 16:40:20 +0300 Subject: [PATCH 3/5] copy and paste added --- .../utilitty/removePossibilityToEnterSigns.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/features/utilitty/removePossibilityToEnterSigns.ts b/src/features/utilitty/removePossibilityToEnterSigns.ts index 55e3f980..a0916862 100644 --- a/src/features/utilitty/removePossibilityToEnterSigns.ts +++ b/src/features/utilitty/removePossibilityToEnterSigns.ts @@ -11,12 +11,14 @@ export const restrictNumericInput = (e: React.KeyboardEvent) = 'ArrowDown', 'ArrowLeft', 'ArrowRight', + 'Control', + 'v', + 'c', ]; if ( - key === 'e' || - key === 'E' || - (isNaN(Number(key)) && key !== '.' && !allowedControlKeys.includes(key)) + (key === 'e' || key === 'E' || (isNaN(Number(key)) && key !== '.' && !allowedControlKeys.includes(key))) + && !(e.ctrlKey && (key === 'v' || key === 'c')) ) { e.preventDefault(); } @@ -27,3 +29,11 @@ export const restrictNumericInput = (e: React.KeyboardEvent) = selectedDot = key === '.'; }; + +export const handlePaste = (e: React.ClipboardEvent) => { + const pasteData = e.clipboardData.getData('text'); + + if (isNaN(Number(pasteData)) || (pasteData.split('.').length - 1 > 1)) { + e.preventDefault(); + } +}; From 08efdc782447bb47c04334d2578b38d26fbdeaa6 Mon Sep 17 00:00:00 2001 From: oak Date: Mon, 9 Sep 2024 17:03:45 +0300 Subject: [PATCH 4/5] wip --- proto | 2 +- .../genericProductComponent/basicFields/basicFields.tsx | 3 +++ .../sizesAndMeasurements/mappingMeasurementsForCategories.ts | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/proto b/proto index 982eed10..6a6084cf 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 982eed109c9a6942c08aadb794dea82d962dedfe +Subproject commit 6a6084cfbed1e5e21bb05425dbc2f54bc5a654c0 diff --git a/src/components/managers/products/genericProductComponent/basicFields/basicFields.tsx b/src/components/managers/products/genericProductComponent/basicFields/basicFields.tsx index e4aef582..77ac2052 100644 --- a/src/components/managers/products/genericProductComponent/basicFields/basicFields.tsx +++ b/src/components/managers/products/genericProductComponent/basicFields/basicFields.tsx @@ -133,6 +133,9 @@ export const BasicFields: FC = ({ return parseWellKnownTimestamp(dateString || '0001-01-01T00:00:00Z'); }; + console.log(dictionary?.categories?.length); // Should log 20 + console.log(dictionary?.categories); // Inspect if any category is missing + useEffect(() => { const { salePercentage, preorder } = values.product?.productBody || {}; const saleValue = salePercentage?.value || ''; diff --git a/src/components/managers/products/genericProductComponent/sizesAndMeasurements/mappingMeasurementsForCategories.ts b/src/components/managers/products/genericProductComponent/sizesAndMeasurements/mappingMeasurementsForCategories.ts index 2053d246..840b77bc 100644 --- a/src/components/managers/products/genericProductComponent/sizesAndMeasurements/mappingMeasurementsForCategories.ts +++ b/src/components/managers/products/genericProductComponent/sizesAndMeasurements/mappingMeasurementsForCategories.ts @@ -86,6 +86,10 @@ export const categoryMeasurementsMapping: { [key in common_CategoryEnum]?: commo CATEGORY_ENUM_OTHER: [ "MEASUREMENT_NAME_ENUM_LENGTH", "MEASUREMENT_NAME_ENUM_WIDTH" + ], + CATEGORY_ENUM_BAG: [ + "MEASUREMENT_NAME_ENUM_HEIGHT", + "MEASUREMENT_NAME_ENUM_WIDTH" ] }; \ No newline at end of file From fc655b6bab0d19b810c635276e4a791e9268fe4d Mon Sep 17 00:00:00 2001 From: oak Date: Mon, 9 Sep 2024 21:20:42 +0300 Subject: [PATCH 5/5] pull master --- .../basicFields/basicFields.tsx | 24 ++++--------------- .../genericProductComponent.tsx | 21 +++++++++------- .../utility/genderList.ts | 7 ++++++ .../utility/preorderTime.ts | 13 ++++++++++ .../filterProducts/filterProducts.tsx | 6 ++--- src/components/managers/products/products.tsx | 3 +-- 6 files changed, 42 insertions(+), 32 deletions(-) create mode 100644 src/components/managers/products/genericProductComponent/utility/genderList.ts create mode 100644 src/components/managers/products/genericProductComponent/utility/preorderTime.ts diff --git a/src/components/managers/products/genericProductComponent/basicFields/basicFields.tsx b/src/components/managers/products/genericProductComponent/basicFields/basicFields.tsx index 77ac2052..3326b11e 100644 --- a/src/components/managers/products/genericProductComponent/basicFields/basicFields.tsx +++ b/src/components/managers/products/genericProductComponent/basicFields/basicFields.tsx @@ -14,7 +14,6 @@ import { DatePicker, LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; import { common_ProductNew } from 'api/proto-http/admin'; import { colors } from 'constants/colors'; -import { isValid, parseISO } from 'date-fns'; import { generateOrUpdateSKU, generateSKU } from 'features/utilitty/dynamicGenerationOfSku'; import { findInDictionary } from 'features/utilitty/findInDictionary'; import { restrictNumericInput } from 'features/utilitty/removePossibilityToEnterSigns'; @@ -24,17 +23,8 @@ import CountryList from 'react-select-country-list'; import { v4 as uuidv4 } from 'uuid'; import { BasicProductFieldsInterface, Country } from '../interface/interface'; import { handleKeyDown } from '../utility/brandNameRegExp'; - -const parseWellKnownTimestamp = (timestamp: string): Date | null => { - if (!timestamp || timestamp === '0001-01-01T00:00:00Z') return null; - const parsedDate = parseISO(timestamp); - return isValid(parsedDate) ? parsedDate : null; -}; - -const formatWellKnownTimestamp = (date: Date | null): string => { - if (!date) return '0001-01-01T00:00:00Z'; - return date.toISOString(); -}; +import { genderOptions } from '../utility/genderList'; +import { formatWellKnownTimestamp, parseWellKnownTimestamp } from '../utility/preorderTime'; export const BasicFields: FC = ({ dictionary, @@ -43,8 +33,7 @@ export const BasicFields: FC = ({ isAddingProduct, isCopyMode, }) => { - const { values, setFieldValue, submitCount, errors, touched } = - useFormikContext(); + const { values, setFieldValue, errors, touched } = useFormikContext(); const countries = useMemo(() => CountryList().getData() as Country[], []); const [showPreorder, setShowPreorder] = useState(true); const [showSales, setShowSales] = useState(true); @@ -133,9 +122,6 @@ export const BasicFields: FC = ({ return parseWellKnownTimestamp(dateString || '0001-01-01T00:00:00Z'); }; - console.log(dictionary?.categories?.length); // Should log 20 - console.log(dictionary?.categories); // Inspect if any category is missing - useEffect(() => { const { salePercentage, preorder } = values.product?.productBody || {}; const saleValue = salePercentage?.value || ''; @@ -239,9 +225,9 @@ export const BasicFields: FC = ({ name='product.productBody.targetGender' disabled={disableFields} > - {dictionary?.genders?.map((gender) => ( + {genderOptions.map((gender) => ( - {gender.name?.replace('GENDER_ENUM_', '').toUpperCase()} + {gender.name?.toUpperCase()} ))} diff --git a/src/components/managers/products/genericProductComponent/genericProductComponent.tsx b/src/components/managers/products/genericProductComponent/genericProductComponent.tsx index 283c4d00..fbac67c3 100644 --- a/src/components/managers/products/genericProductComponent/genericProductComponent.tsx +++ b/src/components/managers/products/genericProductComponent/genericProductComponent.tsx @@ -24,8 +24,8 @@ export const GenericProductForm: FC = ({ }) => { const [isFormChanged, setIsFormChanged] = useState(false); const [clearMediaPreview, setClearMediaPreview] = useState(false); - const navigate = useNavigate(); const initialValues = useMemo(() => initialProductState, [initialProductState]); + const navigate = useNavigate(); useEffect(() => { const handleKeydown = (event: KeyboardEvent) => { @@ -58,6 +58,8 @@ export const GenericProductForm: FC = ({ if (isAddingProduct && !isCopyMode) { setClearMediaPreview(true); setTimeout(() => setClearMediaPreview(false), 0); + } else if (isCopyMode) { + navigate({ to: ROUTES.product, replace: true }); } }; @@ -88,13 +90,15 @@ export const GenericProductForm: FC = ({ sx={{ top: 'auto', bottom: 0, backgroundColor: 'transparent', boxShadow: 'none' }} > - + {!isAddingProduct && ( + + )}