From 5825efd2d539540b8c26d80fea43b7d6d0165115 Mon Sep 17 00:00:00 2001 From: lavanya-bmw Date: Tue, 11 Jul 2023 15:33:05 +0530 Subject: [PATCH 1/5] sub-menu(description): Change Description functionality added for active apps --- CHANGELOG.md | 1 + src/assets/locales/de/main.json | 12 + src/assets/locales/en/main.json | 12 + .../pages/AppOverview/ChangeDescription.tsx | 324 +++++++++++++++++- src/components/pages/AppOverview/index.tsx | 1 + src/features/appManagement/apiSlice.ts | 12 + 6 files changed, 361 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d93961bc3..859c54594 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - Change Image(sub menu) functionality added for active apps - App overview 'in review' style fix - Enhance Sub Menu by adding 'Change Description' for active apps + - Change Description(sub menu) functionality added for active apps - Bugfix - Service Release Process - Service Release process not working diff --git a/src/assets/locales/de/main.json b/src/assets/locales/de/main.json index 3625316fb..b233138a7 100644 --- a/src/assets/locales/de/main.json +++ b/src/assets/locales/de/main.json @@ -415,6 +415,18 @@ "successMsg": "successfully changed app lead image", "errorMsg": "Unable to change the lead image" }, + "changeDescription": { + "headerTitle": "Change Descriptions", + "description": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard .Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard .", + "longDescription": "Long Description", + "longDescriptionEN": "Long Description - EN *", + "longDescriptionDE": "Long Description - DE *", + "shortDescription": "Short Description", + "shortDescriptionEN": "Short Description - EN *", + "shortDescriptionDE": "Short Description - DE *", + "successMsg": "successfully changed description", + "errorMsg": "Unable to change description" + }, "appreleaseprocess": { "message": "App Release Process Message" }, diff --git a/src/assets/locales/en/main.json b/src/assets/locales/en/main.json index 32f22aafe..b42e3d027 100644 --- a/src/assets/locales/en/main.json +++ b/src/assets/locales/en/main.json @@ -414,6 +414,18 @@ "successMsg": "successfully changed app lead image", "errorMsg": "Unable to change the lead image" }, + "changeDescription": { + "headerTitle": "Change Descriptions", + "description": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard .Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard .", + "longDescription": "Long Description", + "longDescriptionEN": "Long Description - EN *", + "longDescriptionDE": "Long Description - DE *", + "shortDescription": "Short Description", + "shortDescriptionEN": "Short Description - EN *", + "shortDescriptionDE": "Short Description - DE *", + "successMsg": "successfully changed description", + "errorMsg": "Unable to change description" + }, "appreleaseprocess": { "message": "App Release Process Message" }, diff --git a/src/components/pages/AppOverview/ChangeDescription.tsx b/src/components/pages/AppOverview/ChangeDescription.tsx index bcd23253f..af2b1a568 100644 --- a/src/components/pages/AppOverview/ChangeDescription.tsx +++ b/src/components/pages/AppOverview/ChangeDescription.tsx @@ -17,6 +17,328 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +import { PageBreadcrumb } from 'components/shared/frame/PageBreadcrumb/PageBreadcrumb' +import { + Typography, + PageHeader, + Button, + LoadingButton, + TabPanel, +} from '@catena-x/portal-shared-components' +import { useTranslation } from 'react-i18next' +import { useLocation, useNavigate, useParams } from 'react-router-dom' +import { Box, IconButton, Tab, Tabs } from '@mui/material' +import { useEffect, useMemo, useState } from 'react' +import { error, success } from 'services/NotifyService' +import ConnectorFormInputFieldShortAndLongDescription from 'components/shared/basic/ReleaseProcess/components/ConnectorFormInputFieldShortAndLongDescription' +import Patterns from 'types/Patterns' +import { useForm } from 'react-hook-form' +import HelpOutlineIcon from '@mui/icons-material/HelpOutline' +import { + useFetchDescriptionQuery, + useSaveDescriptionMutation, +} from 'features/appManagement/apiSlice' +import TextSnippetOutlinedIcon from '@mui/icons-material/TextSnippetOutlined' + export default function ChangeDescription() { - return <>In Progress + const { t } = useTranslation() + const navigate = useNavigate() + const appId = useParams().appId + const [isLoading, setIsLoading] = useState(false) + const { state } = useLocation() + const items: any = state + const app = items?.filter((item: any) => item.id === appId) + const [activeTab, setActiveTab] = useState(0) + const longDescriptionMaxLength = 2000 + const { data: description, refetch } = useFetchDescriptionQuery(appId ?? '') + const [saveDescription] = useSaveDescriptionMutation() + + const defaultValues = useMemo(() => { + return { + longDescriptionEN: description?.[0].longDescription, + longDescriptionDE: description?.[1].longDescription, + shortDescriptionEN: description?.[0].shortDescription, + shortDescriptionDE: description?.[1].shortDescription, + } + }, [state, description]) + + useEffect(() => { + refetch() + reset(defaultValues) + }, [state, description]) + + const { + handleSubmit, + control, + trigger, + formState: { errors, isDirty }, + getValues, + reset, + } = useForm({ + defaultValues: defaultValues, + mode: 'onChange', + }) + + const handleSave = async (data: any) => { + setIsLoading(true) + + const saveData = { + appId: appId, + body: [ + { + languageCode: 'de', + longDescription: getValues().longDescriptionDE, + shortDescription: getValues().shortDescriptionDE, + }, + { + languageCode: 'en', + longDescription: getValues().longDescriptionEN, + shortDescription: getValues().shortDescriptionEN, + }, + ], + } + + await saveDescription(saveData) + .unwrap() + .then(() => { + navigate('/appoverview', { + state: 'change-description-success', + }) + success(t('content.changeDescription.successMsg')) + }) + .catch((err) => { + setIsLoading(false) + error(t('content.changeDescription.errorMsg'), '', err) + }) + } + + const handleChange = (event: any, newValue: number) => { + setActiveTab(newValue) + } + + const patternValidation = (item: string) => { + if ( + (item === 'longDescriptionEN' && + /[ @=<>*\-+#?%&_:;]/.test(getValues().longDescriptionEN?.charAt(0))) || + item === 'longDescriptionEN' + ) { + return `${t( + 'content.apprelease.appReleaseForm.validCharactersIncludes' + )} a-zA-Z0-9 !?@&#'"()[]_-+=<>/*.,;:% and should not start with @=<>*-+ #?%&_:;` + } else { + return `${t( + 'content.apprelease.appReleaseForm.validCharactersIncludes' + )} a-zA-ZÀ-ÿ0-9 !?@&#'"()[]_-+=<>/*.,;:% and should not start with @=<>*-+ #?%&_:;` + } + } + + return ( +
+ + + +
+ + {app?.[0]?.title} + + + {t('content.changeDescription.headerTitle')} + + + {t('content.changeDescription.description')} + +
+
+
+ + } + id={`simple-tab-${activeTab}`} + aria-controls={`simple-tabpanel-${activeTab}`} + iconPosition="start" + /> + } + id={`simple-tab-${activeTab}`} + aria-controls={`simple-tabpanel-${activeTab}`} + iconPosition="start" + /> + + + +
+ {['longDescriptionEN', 'longDescriptionDE'].map( + (item: string) => ( +
+ + {item === 'longDescriptionEN' + ? t('content.changeDescription.longDescriptionEN') + : t('content.changeDescription.longDescriptionDE')} + + + + + } + value={ + (item === 'longDescriptionEN' + ? getValues().longDescriptionEN?.length || 0 + : getValues().longDescriptionDE?.length || 0) + + `/${longDescriptionMaxLength}` + } + patternKey="longDescriptionEN" + patternEN={Patterns.appPage.longDescriptionEN} + patternDE={Patterns.appPage.longDescriptionDE} + rules={{ + required: + t(`content.apprelease.appPage.${item}`) + + t('content.apprelease.appReleaseForm.isMandatory'), + minLength: `${t( + 'content.apprelease.appReleaseForm.minimum' + )} 10 ${t( + 'content.apprelease.appReleaseForm.charactersRequired' + )}`, + pattern: patternValidation(item), + maxLength: `${t( + 'content.apprelease.appReleaseForm.maximum' + )} ${longDescriptionMaxLength} ${t( + 'content.apprelease.appReleaseForm.charactersAllowed' + )}`, + }} + maxLength={longDescriptionMaxLength} + /> +
+ ) + )} +
+
+ +
+ {['shortDescriptionEN', 'shortDescriptionDE'].map( + (item: string, i) => ( +
+ + {item === 'shortDescriptionEN' + ? t('content.changeDescription.shortDescriptionEN') + : t('content.changeDescription.shortDescriptionDE')} + + + + + } + value={ + (item === 'shortDescriptionEN' + ? getValues().shortDescriptionEN?.length || 0 + : getValues().shortDescriptionDE?.length || 0) + + '/255' + } + patternKey="shortDescriptionEN" + patternEN={Patterns.appMarketCard.shortDescriptionEN} + patternDE={Patterns.appMarketCard.shortDescriptionDE} + rules={{ + required: `${t( + `content.apprelease.appMarketCard.${item}` + )} ${t( + 'content.apprelease.appReleaseForm.isMandatory' + )}`, + minLength: `${t( + 'content.apprelease.appReleaseForm.minimum' + )} 10 ${t( + 'content.apprelease.appReleaseForm.charactersRequired' + )}`, + pattern: `${t( + 'content.apprelease.appReleaseForm.validCharactersIncludes' + )} ${ + item === 'shortDescriptionEN' + ? 'a-zA-Z0-9 !?@&#\'"()_-=/*.,;:' + : 'a-zA-ZÀ-ÿ0-9 !?@&#\'"()_-=/*.,;:' + }`, + maxLength: `${t( + 'content.apprelease.appReleaseForm.maximum' + )} 255 ${t( + 'content.apprelease.appReleaseForm.charactersAllowed' + )}`, + }} + /> +
+ ) + )} +
+
+
+
+
+
+ + + + + {isLoading ? ( + {}} + loadIndicator="Loading..." + label={`${t('global.actions.confirm')}`} + /> + ) : ( + + )} + + +
+
+ ) } diff --git a/src/components/pages/AppOverview/index.tsx b/src/components/pages/AppOverview/index.tsx index 9ef36c294..9f62d1502 100644 --- a/src/components/pages/AppOverview/index.tsx +++ b/src/components/pages/AppOverview/index.tsx @@ -74,6 +74,7 @@ export default function AppOverview() { useEffect(() => { state === 'deactivate-success' && refetch() state === 'change-image-success' && refetch() + state === 'change-description-success' && refetch() }, [state, refetch]) useEffect(() => { diff --git a/src/features/appManagement/apiSlice.ts b/src/features/appManagement/apiSlice.ts index 27924f57d..2442b3727 100644 --- a/src/features/appManagement/apiSlice.ts +++ b/src/features/appManagement/apiSlice.ts @@ -365,6 +365,16 @@ export const apiSlice = createApi({ }, invalidatesTags: [Tags.APP], }), + fetchDescription: builder.query({ + query: (appId) => `/api/apps/AppChange/${appId}/appupdate/description`, + }), + saveDescription: builder.mutation({ + query: (data) => ({ + url: `/api/apps/AppChange/${data.appId}/appupdate/description`, + method: 'PUT', + body: data.body, + }), + }), }), }) @@ -392,4 +402,6 @@ export const { useFetchTechnicalUserProfilesQuery, useSaveTechnicalUserProfilesMutation, useUpdateImageDataMutation, + useFetchDescriptionQuery, + useSaveDescriptionMutation, } = apiSlice From 157bc72448214e8f81f02637fb3c74be7a23c904 Mon Sep 17 00:00:00 2001 From: lavanya-bmw Date: Tue, 11 Jul 2023 15:59:52 +0530 Subject: [PATCH 2/5] sub-menu(description): Change Description functionality added for active apps --- .../pages/AppOverview/ChangeDescription.tsx | 68 ++++++++++--------- src/features/appManagement/apiSlice.ts | 15 +++- 2 files changed, 48 insertions(+), 35 deletions(-) diff --git a/src/components/pages/AppOverview/ChangeDescription.tsx b/src/components/pages/AppOverview/ChangeDescription.tsx index af2b1a568..3657947fe 100644 --- a/src/components/pages/AppOverview/ChangeDescription.tsx +++ b/src/components/pages/AppOverview/ChangeDescription.tsx @@ -55,12 +55,12 @@ export default function ChangeDescription() { const defaultValues = useMemo(() => { return { - longDescriptionEN: description?.[0].longDescription, - longDescriptionDE: description?.[1].longDescription, - shortDescriptionEN: description?.[0].shortDescription, - shortDescriptionDE: description?.[1].shortDescription, + longDescriptionEN: description?.[0].longDescription || '', + longDescriptionDE: description?.[1].longDescription || '', + shortDescriptionEN: description?.[0].shortDescription || '', + shortDescriptionDE: description?.[1].shortDescription || '', } - }, [state, description]) + }, [description]) useEffect(() => { refetch() @@ -71,7 +71,7 @@ export default function ChangeDescription() { handleSubmit, control, trigger, - formState: { errors, isDirty }, + formState: { errors, isDirty, isValid }, getValues, reset, } = useForm({ @@ -82,34 +82,36 @@ export default function ChangeDescription() { const handleSave = async (data: any) => { setIsLoading(true) - const saveData = { - appId: appId, - body: [ - { - languageCode: 'de', - longDescription: getValues().longDescriptionDE, - shortDescription: getValues().shortDescriptionDE, - }, - { - languageCode: 'en', - longDescription: getValues().longDescriptionEN, - shortDescription: getValues().shortDescriptionEN, - }, - ], - } + if (appId) { + const saveData = { + appId: appId, + body: [ + { + languageCode: 'de', + longDescription: getValues().longDescriptionDE, + shortDescription: getValues().shortDescriptionDE, + }, + { + languageCode: 'en', + longDescription: getValues().longDescriptionEN, + shortDescription: getValues().shortDescriptionEN, + }, + ], + } - await saveDescription(saveData) - .unwrap() - .then(() => { - navigate('/appoverview', { - state: 'change-description-success', + await saveDescription(saveData) + .unwrap() + .then(() => { + navigate('/appoverview', { + state: 'change-description-success', + }) + success(t('content.changeDescription.successMsg')) }) - success(t('content.changeDescription.successMsg')) - }) - .catch((err) => { - setIsLoading(false) - error(t('content.changeDescription.errorMsg'), '', err) - }) + .catch((err) => { + setIsLoading(false) + error(t('content.changeDescription.errorMsg'), '', err) + }) + } } const handleChange = (event: any, newValue: number) => { @@ -330,7 +332,7 @@ export default function ChangeDescription() { @@ -317,17 +317,17 @@ export default function ChangeDescription() { {isLoading ? ( {}} - loadIndicator="Loading..." + loading={isLoading} label={`${t('global.actions.confirm')}`} + loadIndicator="Loading..." /> ) : (