diff --git a/CHANGELOG.md b/CHANGELOG.md index 40723f874..a5611bf11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## (unreleased) 2.2.0-RC3 + ## Unreleased ### Bugfixes @@ -31,6 +33,7 @@ - **Company Subscriptions** - Update and bind API with filter options [#1062](https://github.com/eclipse-tractusx/portal-frontend/pull/1062) + - Add a tabular section to show up both app and service subscription data [#1101](https://github.com/eclipse-tractusx/portal-frontend/pull/1101) ### Bugfixes diff --git a/src/assets/locales/de/main.json b/src/assets/locales/de/main.json index 4b741e55f..9517608c6 100644 --- a/src/assets/locales/de/main.json +++ b/src/assets/locales/de/main.json @@ -453,8 +453,8 @@ "technicalUser": "Technischer Benutzer", "unsubscribe": "Abbestellen", "table": { - "appIcon": "App-Symbol", - "appTitle": "App-Titel", + "appIcon": "Symbol", + "appTitle": "Titel", "status": "Status", "details": "Einzelheiten", "action": "Aktion" @@ -464,7 +464,8 @@ "active": "aktiv", "inactive": "inaktiv", "showAll": "Alles anzeigen" - } + }, + "searchName": "..search by company name here" }, "companySubscriptionsDetail": { "language": "Verfügbare App Sprachen", diff --git a/src/assets/locales/en/main.json b/src/assets/locales/en/main.json index a5e7a550d..06080b3b2 100644 --- a/src/assets/locales/en/main.json +++ b/src/assets/locales/en/main.json @@ -449,8 +449,8 @@ "technicalUser": "Technical User", "unsubscribe": "Unsubscribe", "table": { - "appIcon": "App Icon", - "appTitle": "App title", + "appIcon": "Icon", + "appTitle": "Title", "status": "Status", "details": "Details", "action": "Action" @@ -460,7 +460,8 @@ "active": "active", "inactive": "inactive", "showAll": "show all" - } + }, + "searchName": "..search by company name here" }, "companySubscriptionsDetail": { "language": "Language", diff --git a/src/components/pages/CompanySubscriptions/CompanySubscriptionDetail.tsx b/src/components/pages/CompanySubscriptions/CompanySubscriptionDetail.tsx index 359b9b06a..e1c59960e 100644 --- a/src/components/pages/CompanySubscriptions/CompanySubscriptionDetail.tsx +++ b/src/components/pages/CompanySubscriptions/CompanySubscriptionDetail.tsx @@ -17,37 +17,75 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -import { BackButton } from '@catena-x/portal-shared-components' +import { BackButton, LogoGrayData } from '@catena-x/portal-shared-components' import { useLocation, useNavigate } from 'react-router-dom' import { useTranslation } from 'react-i18next' -import { Box } from '@mui/material' +import { Box, Typography } from '@mui/material' import { useFetchAppDetailsQuery, useFetchSubscriptionAppQuery, } from 'features/apps/apiSlice' import { PAGES } from 'types/Constants' +import './CompanySubscriptions.scss' +import { + useFetchServiceDetailsQuery, + useFetchSubscriptionServiceQuery, +} from 'features/serviceSubscription/serviceSubscriptionApiSlice' +import CommonService from 'services/CommonService' +import { useState, useEffect } from 'react' +import { getApiBase } from 'services/EnvironmentService' import CompanySubscriptionTechnical from './components/CompanySubscriptionTechnical' -import CompanySubscriptionContent from './components/CompanySubscriptionContent' import CompanySubscriptionHeader from './components/CompanySubscriptionHeader' -import CompanySubscriptionDocument from './components/CompanySubscriptionDocument' +import CompanySubscriptionContent from './components/CompanySubscriptionContent' import CompanySubscriptionPrivacy from './components/CompanySubscriptionPrivacyContent' -import './CompanySubscriptions.scss' +import CompanySubscriptionDocument from './components/CompanySubscriptionDocument' export default function CompanySubscriptionDetail() { const navigate = useNavigate() const { state: items } = useLocation() const { t } = useTranslation() + const id = items.row.offerId ?? ('' as string) + const subscriptionId = items.row.subscriptionId ?? ('' as string) + const { data: appData, error: appError } = useFetchSubscriptionAppQuery( + { appId: id, subscriptionId }, + { skip: items.service } + ) + const { data: serviceData, error: serviceError } = + useFetchSubscriptionServiceQuery( + { serviceId: id, subscriptionId }, + { skip: items.app } + ) + const { data: fetchAppsData } = useFetchAppDetailsQuery(id, { + skip: items.service, + }) + const { data: fetchServicessData } = useFetchServiceDetailsQuery(id, { + skip: items.app, + }) + const [docId, setDocId] = useState('') + + const data = items.app ? appData : serviceData + const fetchData = items.app ? fetchAppsData : fetchServicessData - const appId = items ? items.offerId : '' - const subscriptionId = items ? items.subscriptionId : '' + // To-Do fix the type issue with status and data from FetchBaseQueryError + // eslint-disable-next-line + const error: any = items.app ? appError : serviceError - // Prevent API call when appId does not exist - const { data } = appId - ? useFetchSubscriptionAppQuery({ appId, subscriptionId }) - : { data: undefined } - const { data: fetchAppsData } = appId - ? useFetchAppDetailsQuery(appId) - : { data: undefined } + useEffect(() => { + if (fetchData?.leadPictureId) { + const id = CommonService.isValidPictureId(fetchData?.leadPictureId) + setDocId(id) + } + }, [fetchData]) + + const getSrc = () => { + if (fetchData?.id && items.app && docId) + return `${getApiBase()}/api/apps/${fetchData.id}/appDocuments/${docId}` + if (fetchData?.id && items.service && docId) + return `${getApiBase()}/api/services/${ + fetchData.id + }/serviceDocuments/${docId}` + return LogoGrayData + } return (
@@ -61,12 +99,14 @@ export default function CompanySubscriptionDetail() { }} /> - {data && fetchAppsData && ( + {error && {error?.data?.title}} + + {data && fetchData && ( <> - - - - + + + + )} diff --git a/src/components/pages/CompanySubscriptions/CompanySubscriptionsTableColumns.tsx b/src/components/pages/CompanySubscriptions/CompanySubscriptionsTableColumns.tsx index 87029a91f..58b0008e1 100644 --- a/src/components/pages/CompanySubscriptions/CompanySubscriptionsTableColumns.tsx +++ b/src/components/pages/CompanySubscriptions/CompanySubscriptionsTableColumns.tsx @@ -43,7 +43,8 @@ import './CompanySubscriptions.scss' export const CompanySubscriptionsTableColumns = ( t: typeof i18next.t, - handleOverlay?: (row: SubscribedActiveApps, enable: boolean) => void + handleOverlay?: (row: SubscribedActiveApps, enable: boolean) => void, + currentActive?: number ): Array => { const navigate = useNavigate() @@ -86,6 +87,19 @@ export const CompanySubscriptionsTableColumns = ( ) } + const canShowButton = (row: SubscribedActiveApps) => + row.status === SubscriptionStatus.ACTIVE + + const getSource = (row: SubscribedActiveApps) => { + if (row.image && currentActive === 0) + return `${getApiBase()}/api/apps/${row.offerId}/appDocuments/${row.image}` + if (row.image && currentActive === 1) + return `${getApiBase()}/api/services/${row.offerId}/serviceDocuments/${ + row.image + }` + return LogoGrayData + } + return [ { field: 'image', @@ -96,13 +110,7 @@ export const CompanySubscriptionsTableColumns = ( disableColumnMenu: true, renderCell: ({ row }: { row: SubscribedActiveApps }) => ( { navigate( `/${PAGES.COMPANY_SUBSCRIPTIONS_DETAIL}/${row.offerId}`, - { state: row } + { + state: { + row, + app: currentActive === 0, + service: currentActive === 1, + }, + } ) }} > @@ -161,21 +175,20 @@ export const CompanySubscriptionsTableColumns = ( flex: 2, disableColumnMenu: true, sortable: false, - renderCell: ({ row }: { row: SubscribedActiveApps }) => - row.status === SubscriptionStatus.ACTIVE && ( - - ), + renderCell: ({ row }: { row: SubscribedActiveApps }) => ( + + ), }, ] } diff --git a/src/components/pages/CompanySubscriptions/components/CompanySubscriptionContent/index.tsx b/src/components/pages/CompanySubscriptions/components/CompanySubscriptionContent/index.tsx index d984d8c3a..624e13e84 100644 --- a/src/components/pages/CompanySubscriptions/components/CompanySubscriptionContent/index.tsx +++ b/src/components/pages/CompanySubscriptions/components/CompanySubscriptionContent/index.tsx @@ -22,11 +22,12 @@ import { Box, useMediaQuery, useTheme } from '@mui/material' import { ImageGallery, Typography } from '@catena-x/portal-shared-components' import { type AppDetails } from 'features/apps/types' import CommonService from 'services/CommonService' +import { type ServiceDetailsResponse } from 'features/serviceSubscription/serviceSubscriptionApiSlice' export default function CompanySubscriptionContent({ detail, }: Readonly<{ - detail: AppDetails + detail: AppDetails | ServiceDetailsResponse }>) { const { t } = useTranslation() const theme = useTheme() @@ -44,14 +45,16 @@ export default function CompanySubscriptionContent({ - + {detail?.images && ( + + )} ) diff --git a/src/components/pages/CompanySubscriptions/components/CompanySubscriptionDocument/index.tsx b/src/components/pages/CompanySubscriptions/components/CompanySubscriptionDocument/index.tsx index c996573c0..514496568 100644 --- a/src/components/pages/CompanySubscriptions/components/CompanySubscriptionDocument/index.tsx +++ b/src/components/pages/CompanySubscriptions/components/CompanySubscriptionDocument/index.tsx @@ -26,11 +26,12 @@ import { useFetchDocumentByIdMutation } from 'features/apps/apiSlice' import { type AppDetails, type Documents } from 'features/apps/types' import { download } from 'utils/downloadUtils' import 'components/styles/document.scss' +import { type ServiceDetailsResponse } from 'features/serviceSubscription/serviceSubscriptionApiSlice' export default function CompanySubscriptionDocument({ detail, }: Readonly<{ - detail: AppDetails + detail: AppDetails | ServiceDetailsResponse }>) { const { t } = useTranslation() diff --git a/src/components/pages/CompanySubscriptions/components/CompanySubscriptionHeader/index.tsx b/src/components/pages/CompanySubscriptions/components/CompanySubscriptionHeader/index.tsx index 57ed54406..3523d206e 100644 --- a/src/components/pages/CompanySubscriptions/components/CompanySubscriptionHeader/index.tsx +++ b/src/components/pages/CompanySubscriptions/components/CompanySubscriptionHeader/index.tsx @@ -28,26 +28,18 @@ import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline' import UnpublishedIcon from '@mui/icons-material/Unpublished' import HourglassEmptyIcon from '@mui/icons-material/HourglassEmpty' import { useTranslation } from 'react-i18next' -import { useEffect, useState } from 'react' import { type AppDetails, SubscriptionStatus } from 'features/apps/types' -import CommonService from 'services/CommonService' import { fetchImageWithToken } from 'services/ImageService' -import { getApiBase } from 'services/EnvironmentService' +import { type ServiceDetailsResponse } from 'features/serviceSubscription/serviceSubscriptionApiSlice' export default function CompanySubscriptionHeader({ detail, + src, }: Readonly<{ - detail: AppDetails + detail: AppDetails | ServiceDetailsResponse + src: string }>) { const { t } = useTranslation() - const [docId, setDocId] = useState('') - - useEffect(() => { - if (detail.leadPictureId) { - const id = CommonService.isValidPictureId(detail.leadPictureId) - setDocId(id) - } - }, [detail]) const renderStatusButton = (status: string) => { if (status === SubscriptionStatus.ACTIVE) @@ -102,11 +94,7 @@ export default function CompanySubscriptionHeader({
{detail.title} @@ -119,7 +107,7 @@ export default function CompanySubscriptionHeader({ {t('content.companySubscriptionsDetail.language')}: - {detail.languages.length + {detail?.languages?.length ? detail.languages.map((lang, index) => ( {` ${index ? ', ' : ''}${lang.toUpperCase()} `} diff --git a/src/components/pages/CompanySubscriptions/components/CompanySubscriptionPrivacyContent/index.tsx b/src/components/pages/CompanySubscriptions/components/CompanySubscriptionPrivacyContent/index.tsx index f57347048..265d7f2d2 100644 --- a/src/components/pages/CompanySubscriptions/components/CompanySubscriptionPrivacyContent/index.tsx +++ b/src/components/pages/CompanySubscriptions/components/CompanySubscriptionPrivacyContent/index.tsx @@ -24,6 +24,7 @@ import { Typography } from '@catena-x/portal-shared-components' import { PrivacyPolicyType } from 'features/adminBoard/adminBoardApiSlice' import { type AppDetails } from 'features/apps/types' import './CompanySubscriptionPrivacyContent.scss' +import { type ServiceDetailsResponse } from 'features/serviceSubscription/serviceSubscriptionApiSlice' const policyIcons = { [PrivacyPolicyType.COMPANY_DATA]: Apartment, @@ -36,7 +37,7 @@ const policyIcons = { export default function CompanySubscriptionPrivacy({ detail, }: Readonly<{ - detail: AppDetails + detail: AppDetails | ServiceDetailsResponse }>) { const { t } = useTranslation('', { keyPrefix: 'content.companySubscriptionsDetail.privacy', @@ -58,7 +59,7 @@ export default function CompanySubscriptionPrivacy({
{detail?.privacyPolicies?.length ? (
- {detail.privacyPolicies.map((policy: PrivacyPolicyType) => ( + {detail?.privacyPolicies?.map((policy: PrivacyPolicyType) => ( ) { const { t } = useTranslation() diff --git a/src/components/pages/CompanySubscriptions/index.tsx b/src/components/pages/CompanySubscriptions/index.tsx index 9c609877f..e41e53bc5 100644 --- a/src/components/pages/CompanySubscriptions/index.tsx +++ b/src/components/pages/CompanySubscriptions/index.tsx @@ -19,6 +19,9 @@ import { PageLoadingTable, + Tab, + TabPanel, + Tabs, Typography, } from '@catena-x/portal-shared-components' import { useTranslation } from 'react-i18next' @@ -26,7 +29,7 @@ import { useFetchSubscribedActiveAppsStatusQuery, useUnsubscribeAppMutation, } from 'features/apps/apiSlice' -import { useEffect, useState } from 'react' +import { type SyntheticEvent, useEffect, useState } from 'react' import { isName } from 'types/Patterns' import { useDispatch, useSelector } from 'react-redux' import { setSearchInput } from 'features/appManagement/actions' @@ -38,6 +41,11 @@ import { type SubscribedActiveApps, } from 'features/apps/types' import { CompanySubscriptionsTableColumns } from './CompanySubscriptionsTableColumns' +import { + useFetchCompanyServiceSubscriptionsQuery, + useUnsubscribeServiceMutation, +} from 'features/serviceSubscription/serviceSubscriptionApiSlice' +import { Box } from '@mui/material' interface FetchHookArgsType { statusId: string @@ -62,8 +70,10 @@ export default function CompanySubscriptions() { const [loading, setLoading] = useState(false) const [appId, setAppId] = useState('') const [subscriptionId, setSubscriptionId] = useState('') - const [unsubscribeMutation] = useUnsubscribeAppMutation() + const [unsubscribeAppMutation] = useUnsubscribeAppMutation() + const [unsubscribeServiceMutation] = useUnsubscribeServiceMutation() const [enableErrorMessage, setEnableErrorMessage] = useState(false) + const [currentActive, setCurrentActive] = useState(0) const setView = (e: React.MouseEvent) => { const viewValue = e.currentTarget.value @@ -72,6 +82,13 @@ export default function CompanySubscriptions() { setRefresh(Date.now()) } + const handleTabChange = ( + _e: SyntheticEvent, + value: number + ) => { + setCurrentActive(value) + } + const filterView = [ { buttonText: t('content.companySubscriptions.filter.pending'), @@ -108,16 +125,24 @@ export default function CompanySubscriptions() { return validateExpr } + const callSuccess = () => { + success(t('content.organization.unsubscribe.unsubscribeSuccess')) + setLoading(false) + setShowUnsubscribeOverlay(false) + setEnableErrorMessage(false) + setRefresh(Date.now()) + } + const onUnsubscribeSubmit = async () => { setLoading(true) - await unsubscribeMutation(subscriptionId) + await ( + currentActive === 0 + ? unsubscribeAppMutation(subscriptionId) + : unsubscribeServiceMutation(subscriptionId) + ) .unwrap() .then(() => { - success(t('content.organization.unsubscribe.unsubscribeSuccess')) - setLoading(false) - setShowUnsubscribeOverlay(false) - setEnableErrorMessage(false) - setRefresh(Date.now()) + callSuccess() }) .catch(() => { setLoading(false) @@ -133,7 +158,69 @@ export default function CompanySubscriptions() { const companySubscriptionsCols = CompanySubscriptionsTableColumns( t, - handleOverlay + handleOverlay, + currentActive + ) + + const getIcon = (num: number) => { + return ( + + {num} + + ) + } + + // Add an ESLint exception until there is a solution + // eslint-disable-next-line + const renderTable = (query: any) => ( +
+ + sx={{ + '.MuiDataGrid-cell': { + alignContent: 'center !important', + }, + }} + autoFocus={false} + searchExpr={searchExpr} + alignCell="start" + defaultFilter={group} + filterViews={filterView} + toolbarVariant={'searchAndFilter'} + hasBorder={false} + columnHeadersBackgroundColor={'transparent'} + searchPlaceholder={t('content.companySubscriptions.searchName')} + searchInputData={searchInputData} + onSearch={(expr: string) => { + if (expr !== '' && !onValidate(expr)) return + setRefresh(Date.now()) + setSearchExpr(expr) + }} + searchDebounce={1000} + title={''} + loadLabel={t('global.actions.more')} + fetchHook={query} + fetchHookArgs={fetchHookArgs} + fetchHookRefresh={refresh} + getRowId={(row: { [key: string]: string }) => row.offerId} + columns={companySubscriptionsCols} + /> +
) return ( @@ -151,6 +238,7 @@ export default function CompanySubscriptions() { appId={appId} subscriptionId={subscriptionId} enableErrorMessage={enableErrorMessage} + activeTab={currentActive} /> )} {t('content.companySubscriptions.headertitle')} - -
- - sx={{ - '.MuiDataGrid-cell': { - alignContent: 'center !important', - }, - }} - autoFocus={false} - searchExpr={searchExpr} - alignCell="start" - defaultFilter={group} - filterViews={filterView} - toolbarVariant={'searchAndFilter'} - hasBorder={false} - columnHeadersBackgroundColor={'transparent'} - searchPlaceholder={t('global.table.searchName')} - searchInputData={searchInputData} - onSearch={(expr: string) => { - if (!onValidate(expr)) return - setRefresh(Date.now()) - setSearchExpr(expr) - }} - searchDebounce={1000} - title={''} - loadLabel={t('global.actions.more')} - fetchHook={useFetchSubscribedActiveAppsStatusQuery} - fetchHookArgs={fetchHookArgs} - fetchHookRefresh={refresh} - getRowId={(row: { [key: string]: string }) => row.subscriptionId} - columns={companySubscriptionsCols} - /> -
+ + + + + + + {renderTable(useFetchSubscribedActiveAppsStatusQuery)} + + + {renderTable(useFetchCompanyServiceSubscriptionsQuery)} + +
) } diff --git a/src/components/pages/Organization/UnSubscribeOverlay.tsx b/src/components/pages/Organization/UnSubscribeOverlay.tsx index c7e18e135..2fb218a49 100644 --- a/src/components/pages/Organization/UnSubscribeOverlay.tsx +++ b/src/components/pages/Organization/UnSubscribeOverlay.tsx @@ -34,10 +34,12 @@ import { import Box from '@mui/material/Box' import { useFetchSubscriptionAppQuery } from 'features/apps/apiSlice' import './Organization.scss' +import { type SubscribeTechnicalUserData } from 'features/apps/types' import { - type SubscribeTechnicalUserData, - SubscriptionStatus, -} from 'features/apps/types' + OfferSubscriptionStatus, + useFetchSubscriptionServiceQuery, +} from 'features/serviceSubscription/serviceSubscriptionApiSlice' +import LoadingProgress from 'components/shared/basic/LoadingProgress' interface UnSubscribeOverlayProps { openDialog?: boolean @@ -47,6 +49,7 @@ interface UnSubscribeOverlayProps { subscriptionId: string appId: string enableErrorMessage: boolean + activeTab: number } const UnSubscribeOverlay = ({ @@ -57,10 +60,35 @@ const UnSubscribeOverlay = ({ subscriptionId, appId, enableErrorMessage, + activeTab, }: UnSubscribeOverlayProps) => { const { t } = useTranslation() - const { data } = useFetchSubscriptionAppQuery({ appId, subscriptionId }) + const { + data: appData, + error: appError, + isFetching: isAppFetching, + } = useFetchSubscriptionAppQuery( + { appId, subscriptionId }, + { skip: activeTab === 1 } + ) + const { + data: serviceData, + error: serviceError, + isFetching: isServiceFetching, + } = useFetchSubscriptionServiceQuery( + { serviceId: appId, subscriptionId }, + { skip: activeTab === 0 } + ) const [checkBoxSelected, setCheckBoxSelected] = useState(false) + + const data = activeTab === 0 ? appData : serviceData + + const isFetching: boolean = isAppFetching ?? isServiceFetching + + // To-Do fix the type issue with status and data from FetchBaseQueryError + // eslint-disable-next-line + const error: any = activeTab === 0 ? appError : serviceError + return (
- - {t('content.organization.unsubscribe.descriptionNote')} - - - {t('content.organization.unsubscribe.description')} - - - - userdata.name - ) - .toString() ?? '', - ], - ], - }} - horizontal={false} - /> - - - { - setCheckBoxSelected(!checkBoxSelected) - }} - /> - - {t('content.organization.unsubscribe.checkBoxLabelDescription')} + + {t('content.organization.unsubscribe.descriptionNote')} + + {t('content.organization.unsubscribe.description')} + +
+ + {isFetching ? ( +
+ +
+ ) : ( + <> + {error ? ( + + {error.data.title} + + ) : ( + <> + + userdata.name + ) + .toString() ?? '', + ], + ], + }} + horizontal={false} + /> + + { + setCheckBoxSelected(!checkBoxSelected) + }} + /> + + {t( + 'content.organization.unsubscribe.checkBoxLabelDescription' + )} + + + + )} + + )}
@@ -179,7 +244,7 @@ const UnSubscribeOverlay = ({ ) : (