From 939e259d775a582e28c0b7b8f99c1478061b112d Mon Sep 17 00:00:00 2001 From: lavanya-bmw <106523828+lavanya-bmw@users.noreply.github.com> Date: Fri, 25 Aug 2023 12:31:49 +0530 Subject: [PATCH] feat(app overview): implemented add roles (#225) --- src/assets/locales/de/main.json | 18 +- src/assets/locales/en/main.json | 18 +- src/components/pages/AppOverview/AddRoles.tsx | 150 +++++++++ .../pages/AppOverview/AddRolesOverlay.tsx | 299 ++++++++++++++++++ .../pages/AppOverview/AppOverview.scss | 20 ++ .../pages/AppOverview/AppOverviewList.tsx | 16 +- .../pages/AppOverview/ChangeDescription.tsx | 5 +- src/components/pages/AppOverview/index.tsx | 1 + src/features/appManagement/apiSlice.ts | 33 ++ src/types/Config.tsx | 15 + src/types/Constants.ts | 1 + 11 files changed, 565 insertions(+), 11 deletions(-) create mode 100644 src/components/pages/AppOverview/AddRoles.tsx create mode 100644 src/components/pages/AppOverview/AddRolesOverlay.tsx diff --git a/src/assets/locales/de/main.json b/src/assets/locales/de/main.json index 8d8039689..68d5a6960 100644 --- a/src/assets/locales/de/main.json +++ b/src/assets/locales/de/main.json @@ -394,7 +394,8 @@ "sortOptions": { "deactivate": "Deactivate", "changeImage": "Change Image", - "changeDescription": "Change Description" + "changeDescription": "Change Description", + "addRoles": "Add Roles" }, "addbtn": "Registrieren Sie Ihre App", "confirmModal": { @@ -423,7 +424,7 @@ "headerTitle": "Change Lead Image", "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 .", "uploadNewImage": "Upload New Image", - "successMsg": "successfully changed app lead image", + "successMsg": "Successfully changed app lead image", "errorMsg": "Unable to change the lead image" }, "changeDescription": { @@ -435,9 +436,20 @@ "shortDescription": "Short Description", "shortDescriptionEN": "Short Description - EN *", "shortDescriptionDE": "Short Description - DE *", - "successMsg": "successfully changed description", + "successMsg": "Successfully changed description", "errorMsg": "Unable to change description" }, + "addRoles": { + "headerTitle": "Load Additional Roles", + "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 .", + "uploadAdditionalRoles": "Upload Additional Roles", + "establishedRoles": "Established Roles", + "uploadAdditionalRolesDescription": "Note: only one file for upload allowed", + "newRolesToAdd": "New roles to be added", + "confirmButtonDescription": "With clicking the confirm button, the newly added application roles will get added in the top record as well as inside all active apps clients of your app customers.", + "successMsg": "Successfully added roles", + "errorMsg": "Unable to add roles" + }, "appreleaseprocess": { "message": "App Release Process Message" }, diff --git a/src/assets/locales/en/main.json b/src/assets/locales/en/main.json index 1f5284f8b..21b077eb8 100644 --- a/src/assets/locales/en/main.json +++ b/src/assets/locales/en/main.json @@ -393,7 +393,8 @@ "sortOptions": { "deactivate": "Deactivate", "changeImage": "Change Image", - "changeDescription": "Change Description" + "changeDescription": "Change Description", + "addRoles": "Add Roles" }, "addbtn": "Register your App", "confirmModal": { @@ -422,7 +423,7 @@ "headerTitle": "Change Lead Image", "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 .", "uploadNewImage": "Upload New Image", - "successMsg": "successfully changed app lead image", + "successMsg": "Successfully changed app lead image", "errorMsg": "Unable to change the lead image" }, "changeDescription": { @@ -434,9 +435,20 @@ "shortDescription": "Short Description", "shortDescriptionEN": "Short Description - EN *", "shortDescriptionDE": "Short Description - DE *", - "successMsg": "successfully changed description", + "successMsg": "Successfully changed description", "errorMsg": "Unable to change description" }, + "addRoles": { + "headerTitle": "Load Additional Roles", + "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 .", + "uploadAdditionalRoles": "Upload Additional Roles", + "establishedRoles": "Established Roles", + "uploadAdditionalRolesDescription": "Note: only one file for upload allowed", + "newRolesToAdd": "New roles to be added", + "confirmButtonDescription": "With clicking the confirm button, the newly added application roles will get added in the top record as well as inside all active apps clients of your app customers.", + "successMsg": "Successfully added roles", + "errorMsg": "Unable to add roles" + }, "appreleaseprocess": { "message": "App Release Process Message" }, diff --git a/src/components/pages/AppOverview/AddRoles.tsx b/src/components/pages/AppOverview/AddRoles.tsx new file mode 100644 index 000000000..1e740f915 --- /dev/null +++ b/src/components/pages/AppOverview/AddRoles.tsx @@ -0,0 +1,150 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +import { PageBreadcrumb } from 'components/shared/frame/PageBreadcrumb/PageBreadcrumb' +import { + Typography, + PageHeader, + Button, + LoadingButton, + StaticTable, + Checkbox, +} from '@catena-x/portal-shared-components' +import { useTranslation } from 'react-i18next' +import { useLocation, useNavigate, useParams } from 'react-router-dom' +import { Box } from '@mui/material' +import { useEffect, useState } from 'react' +import { TableType } from 'types/MainTypes' +import { useFetchAppRolesQuery } from 'features/appManagement/apiSlice' +import AddRolesOverlay from './AddRolesOverlay' +import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined' +import { PAGES } from 'types/Constants' + +export default function AddRoles() { + 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 { data, refetch } = useFetchAppRolesQuery(appId ?? '') + const [addRolesOverlayOpen, setAddRolesOverlayOpen] = useState(false) + const [appRoles, setAppRoles] = useState([ + [''], + [`${()}`], + ]) + + useEffect(() => { + refetch() + }, [state]) + + const handleSaveClick = async () => { + setIsLoading(true) + } + + useEffect(() => { + setAppRoles( + data && data.length > 0 + ? data.map((role) => [role.role, `${()}`]) + : [['', '']] + ) + }, [data]) + + const tableData: TableType = { + head: [ + t('content.addRoles.establishedRoles'), + `${()}`, + ], + body: appRoles, + } + + return ( +
+ + + +
+ + {app?.[0]?.title} + + + {t('content.addRoles.headerTitle')} + + + {t('content.addRoles.description')} + +
+ setAddRolesOverlayOpen(false)} + appId={appId || ''} + /> +
+
+ + + + +
+
+ +
+
+ + + + + {isLoading ? ( + {}} + label={`${t('global.actions.confirm')}`} + /> + ) : ( + + )} + + +
+
+ ) +} diff --git a/src/components/pages/AppOverview/AddRolesOverlay.tsx b/src/components/pages/AppOverview/AddRolesOverlay.tsx new file mode 100644 index 000000000..2102aafbc --- /dev/null +++ b/src/components/pages/AppOverview/AddRolesOverlay.tsx @@ -0,0 +1,299 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +import { useTranslation } from 'react-i18next' +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogHeader, + LoadingButton, + StaticTable, + TableType, + Typography, +} from '@catena-x/portal-shared-components' +import { PAGES } from 'types/Constants' +import { Grid } from '@mui/material' +import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined' +import { Controller, useForm } from 'react-hook-form' +import { Dropzone } from 'components/shared/basic/Dropzone' +import { isString } from 'lodash' +import { ErrorType } from 'features/appManagement/types' +import { useState } from 'react' +import { useUpdateActiveAppMutation } from 'features/appManagement/apiSlice' +import { useNavigate } from 'react-router-dom' +import { error, success } from 'services/NotifyService' + +interface AddRolesOverlayProps { + openDialog: boolean + handleOverlayClose: () => void + appId: string +} +const AddRolesOverlay = ({ + openDialog = false, + handleOverlayClose, + appId, +}: AddRolesOverlayProps) => { + const { t } = useTranslation() + const navigate = useNavigate() + + const [rolesPreviews, setRolesPreviews] = useState([]) + const [rolesDescription, setRolesDescription] = useState([]) + const [isLoading, setIsLoading] = useState(false) + const [uploadCSVError, setUploadCSVError] = useState(false) + const [updateActiveApp] = useUpdateActiveAppMutation() + const defaultValues = { + uploadAppRoles: '', + } + + const { + control, + trigger, + formState: { errors }, + } = useForm({ + defaultValues: defaultValues, + mode: 'onChange', + }) + + const csvRolesPreview = (files: File[]) => { + return files + .filter((file: File) => file.type === 'text/csv') + .forEach((file: File) => { + const reader = new FileReader() + reader.onerror = () => console.log('file reading has failed') + reader.onabort = () => console.log('file reading was aborted') + reader.onload = () => { + const str = reader.result + if (!isString(str)) return + const CSVCells = str + ?.split('\n') + .filter((item) => item !== '') + .map((item) => item) + + if ( + CSVCells[0] === 'roles;description\r' || + CSVCells[0] === 'roles;description' + ) { + const roleDescription = str + ?.split('\n') + .filter((item) => item !== '') + .map((item) => item.substring(item.indexOf(';') + 1)) + + const roles = str + ?.split('\n') + .filter((item) => item !== '') + .map((item) => item.substring(0, item.indexOf(';'))) + + setRolesPreviews(roles?.splice(1)) + setRolesDescription(roleDescription?.splice(1)) + setUploadCSVError(false) + } else { + setRolesPreviews([]) + setRolesDescription([]) + setUploadCSVError(true) + } + } + reader.readAsText(file) + }) + } + const tableData: TableType = { + head: [t('content.addRoles.newRolesToAdd')], + body: rolesPreviews?.map((item) => [item]), + } + + const handleConfirm = async () => { + setIsLoading(true) + const rolesDescriptionData = rolesPreviews.map((data, i) => [ + data, + rolesDescription[i], + ]) + + const updatedData = { + appId: appId, + body: rolesDescriptionData?.map((item) => ({ + role: item[0], + descriptions: [ + { + languageCode: 'en', + description: item[1], + }, + { + languageCode: 'de', + description: item[1], + }, + ], + })), + } + + await updateActiveApp(updatedData) + .unwrap() + .then(() => { + navigate(`/${PAGES.APPOVERVIEW}`, { + state: 'add-roles-success', + }) + success(t('content.addRoles.successMsg')) + }) + .catch((err) => { + setIsLoading(false) + error(t('content.addRoles.errorMsg'), '', err) + }) + } + + return ( +
+ + { + handleOverlayClose() + }} + /> + + + + + + + + ( + { + if (deletedFiles?.length) { + setRolesDescription([]) + setRolesPreviews([]) + } + reactHookFormOnChange(files[0]?.name) + trigger('uploadAppRoles') + csvRolesPreview(files) + }} + acceptFormat={{ 'text/csv': ['.csv'] }} + maxFilesToUpload={1} + enableDeleteOverlay={true} + deleteOverlayTranslation={{ + title: '', + content: t( + 'content.apprelease.technicalIntegration.deleteOverlayContent' + ), + action_no: `${t('global.actions.no')}`, + action_yes: `${t('global.actions.yes')}`, + }} + /> + )} + /> + {uploadCSVError && ( + + {t( + 'content.apprelease.technicalIntegration.incorrectCSVFileFormat' + )} + + )} + {errors?.uploadAppRoles?.type === ErrorType.REQUIRED && ( + + {t('content.apprelease.appReleaseForm.fileUploadIsMandatory')} + + )} + + {rolesPreviews?.length > 0 && ( + + )} + + {t('content.addRoles.confirmButtonDescription')} + + + + + + {isLoading ? ( + {}} + loading={isLoading} + label={`${t('global.actions.confirm')}`} + loadIndicator="Loading..." + sx={{ ml: 3 }} + /> + ) : ( + + )} + + +
+ ) +} + +export default AddRolesOverlay diff --git a/src/components/pages/AppOverview/AppOverview.scss b/src/components/pages/AppOverview/AppOverview.scss index 609f95fb8..24217c56e 100644 --- a/src/components/pages/AppOverview/AppOverview.scss +++ b/src/components/pages/AppOverview/AppOverview.scss @@ -105,6 +105,20 @@ } } +.add-app-roles-main { + .main-container { + background: #f9f9f9; + padding: 0 0 100px 0; + .main-row { + width: 100%; + max-width: 1100px; + padding: 0 15px 0 15px !important; + margin-right: auto; + margin-left: auto; + } + } +} + .verify-validate-form-divider { margin: 20px auto !important; } @@ -151,3 +165,9 @@ border-radius: 16px; background: #f3f3f3; } + +.file-error-msg { + color: #d32f2f !important; + margin-top: 3px !important; + font-size: 0.75rem !important; +} diff --git a/src/components/pages/AppOverview/AppOverviewList.tsx b/src/components/pages/AppOverview/AppOverviewList.tsx index d5deababb..aed580beb 100644 --- a/src/components/pages/AppOverview/AppOverviewList.tsx +++ b/src/components/pages/AppOverview/AppOverviewList.tsx @@ -29,6 +29,7 @@ enum AppSubMenuItems { DEACTIVATE = 'deactivate', CHANGE_IMAGE = 'changeImage', CHANGE_DESCRIPTION = 'changeDescription', + ADD_ROLES = 'addRoles', } export const AppOverviewList = ({ @@ -57,6 +58,11 @@ export const AppOverviewList = ({ value: AppSubMenuItems.CHANGE_DESCRIPTION, url: '', }, + { + label: t('content.appoverview.sortOptions.addRoles'), + value: AppSubMenuItems.ADD_ROLES, + url: '', + }, ] return ( @@ -79,15 +85,19 @@ export const AppOverviewList = ({ submenuOptions={submenuOptions} submenuClick={(sortMenu: string, id: string | undefined) => { sortMenu === AppSubMenuItems.DEACTIVATE && - navigate(`/deactivate/${id}`, { + navigate(`/${PAGES.DEACTIVATE}/${id}`, { state: filterItem, }) sortMenu === AppSubMenuItems.CHANGE_IMAGE && - navigate(`/changeimage/${id}`, { + navigate(`/${PAGES.CHANGE_IMAGE}/${id}`, { state: filterItem, }) sortMenu === AppSubMenuItems.CHANGE_DESCRIPTION && - navigate(`/changedescription/${id}`, { + navigate(`/${PAGES.CHANGE_DESCRIPTION}/${id}`, { + state: filterItem, + }) + sortMenu === AppSubMenuItems.ADD_ROLES && + navigate(`/${PAGES.ADD_ROLES}/${id}`, { state: filterItem, }) return undefined diff --git a/src/components/pages/AppOverview/ChangeDescription.tsx b/src/components/pages/AppOverview/ChangeDescription.tsx index 1e968c070..881ac924d 100644 --- a/src/components/pages/AppOverview/ChangeDescription.tsx +++ b/src/components/pages/AppOverview/ChangeDescription.tsx @@ -39,6 +39,7 @@ import { } from 'features/appManagement/apiSlice' import HelpOutlineIcon from '@mui/icons-material/HelpOutline' import TextSnippetOutlinedIcon from '@mui/icons-material/TextSnippetOutlined' +import { PAGES } from 'types/Constants' export default function ChangeDescription() { const { t } = useTranslation() @@ -102,7 +103,7 @@ export default function ChangeDescription() { await saveDescription(saveData) .unwrap() .then(() => { - navigate('/appoverview', { + navigate(`/${PAGES.APPOVERVIEW}`, { state: 'change-description-success', }) success(t('content.changeDescription.successMsg')) @@ -311,7 +312,7 @@ export default function ChangeDescription() {