()
+ const { data: session, status } = useSession()
+
+ const columns = [
+ {
+ id: 'licenseClearing.name',
+ name: t('Name'),
+ width: '9%',
+ formatter: ({
+ name,
+ version,
+ id,
+ type,
+ }: {
+ name: string
+ version: string
+ id: string
+ type: ElementType
+ }) =>
+ _(
+
+ {`${name} (${version})`}
+
+ ),
+ sort: true,
+ },
+ {
+ id: 'licenseClearing.type',
+ name: t('Type'),
+ width: '7%',
+ formatter: (type: string) => _(<>{Capitalize(type)}>),
+ sort: true,
+ },
+ {
+ id: 'licenseClearing.projectPath',
+ name: t('Project Path'),
+ width: '12%',
+ formatter: (path: string[]) => _(<>{path.join(' -> ')}>),
+ sort: true,
+ },
+ {
+ id: 'licenseClearing.releasePath',
+ name: t('Release Path'),
+ width: '9%',
+ formatter: (path: string[]) => _(<>{path.join(' -> ')}>),
+ sort: true,
+ },
+ {
+ id: 'licenseClearing.relation',
+ name: t('Relation'),
+ width: '9%',
+ formatter: (type: string) => _(<>{Capitalize(type)}>),
+ sort: true,
+ },
+ {
+ id: 'licenseClearing.mainLicenses',
+ name: t('Main Licenses'),
+ width: '10%',
+ formatter: (licenses: string[]) =>
+ _(
+ <>
+ {licenses.map((e, i) => (
+
+
+ {e}
+
+ {i === licenses.length - 1 ? '' : ','}{' '}
+
+ ))}
+ >
+ ),
+ sort: true,
+ },
+ {
+ id: 'licenseClearing.state',
+ name: t('State'),
+ width: '8%',
+ formatter: ({ state, elementType }: { state: ProjectState | ReleaseState; elementType: ElementType }) => {
+ if (elementType === ElementType.LINKED_PROJECT) {
+ return _(
+ <>
+
+ {`${t('Project State')}: ${Capitalize(
+ (state as ProjectState).state
+ )}`}
+ }
+ >
+ {(state as ProjectState).state === 'ACTIVE' ? (
+ {'PS'}
+ ) : (
+ {'PS'}
+ )}
+
+ {`${t('Project Clearing State')}: ${Capitalize(
+ (state as ProjectState).clearingState
+ )}`}
+ }
+ >
+ {(state as ProjectState).clearingState === 'OPEN' ? (
+ {'CS'}
+ ) : (state as ProjectState).clearingState === 'IN_PROGRESS' ? (
+ {'CS'}
+ ) : (
+ {'CS'}
+ )}
+
+
+ >
+ )
+ }
+ return _(
+ <>
+
+ {`${t('Release Clearing State')}: ${Capitalize(
+ (state as ReleaseState).clearingState
+ )}`}
+ }
+ >
+ {(state as ReleaseState).clearingState === 'NEW_CLEARING' ? (
+ {'CS'}
+ ) : (state as ReleaseState).clearingState === 'REPORT_AVAILABLE' ? (
+ {'CS'}
+ ) : (
+ {'CS'}
+ )}
+
+
+ >
+ )
+ },
+ sort: true,
+ },
+ {
+ id: 'licenseClearing.releaseMainlineState',
+ name: t('Release Mainline State'),
+ width: '8%',
+ formatter: (type: string) => _(<>{Capitalize(type)}>),
+ sort: true,
+ },
+ {
+ id: 'licenseClearing.projectMainlineState',
+ name: t('Project Mainline State'),
+ width: '8%',
+ formatter: (type: string) => _(<>{Capitalize(type)}>),
+ sort: true,
+ },
+ {
+ id: 'licenseClearing.comment',
+ name: t('Comment'),
+ width: '8%',
+ sort: true,
+ },
+ {
+ id: 'licenseClearing.actions',
+ name: t('Actions'),
+ sort: true,
+ width: '6%',
+ formatter: ({ id, type }: { id: string; type: ElementType }) =>
+ _(
+ <>
+ {t('Edit')}}>
+
+
+
+
+ >
+ ),
+ },
+ ]
+
+ useEffect(() => {
+ if (status !== 'authenticated') return
+ const controller = new AbortController()
+ const signal = controller.signal
+
+ ;(async () => {
+ try {
+ const res_licenseClearing = await ApiUtils.GET(
+ `projects/${projectId}/licenseClearing?transitive=true`,
+ session.user.access_token,
+ signal
+ )
+
+ const res_linkedProjects = ApiUtils.GET(
+ `projects/${projectId}/linkedProjects?transitive=true`,
+ session.user.access_token,
+ signal
+ )
+
+ const responses = await Promise.all([res_licenseClearing, res_linkedProjects])
+ if (
+ responses[0].status === HttpStatus.UNAUTHORIZED ||
+ responses[1].status === HttpStatus.UNAUTHORIZED
+ ) {
+ return signOut()
+ } else if (responses[0].status !== HttpStatus.OK || responses[1].status !== HttpStatus.OK) {
+ return notFound()
+ }
+
+ const licenseClearingData = await responses[0].json()
+ const linkedProjectsData = await responses[1].json()
+
+ const finalData: ListViewData[] = []
+ const path: string[] = []
+ extractLinkedReleases(projectName, projectVersion, licenseClearingData, finalData, path)
+ extractLinkedProjectsAndTheirLinkedReleases(
+ licenseClearingData,
+ linkedProjectsData?.['_embedded']?.['sw360:projects'],
+ finalData,
+ path
+ )
+
+ const d = finalData.map((e) => [
+ { ...e.elem, type: e.elementType },
+ e.type,
+ e.projectPath,
+ e.releasePath,
+ e.relation,
+ e.mainLicenses,
+ { state: e.state, elementType: e.elementType },
+ e.releaseMainlineState,
+ e.projectMainlineState,
+ e.comment,
+ { id: e.elem.id, type: e.elementType },
+ ])
+ setData(d)
+ } catch (e) {
+ console.error(e)
+ }
+ })()
+
+ return () => controller.abort()
+ }, [status])
+
+ return (
+ <>
+ {data ? (
+
+ ) : (
+
+
+
+ )}
+ >
+ )
+}
diff --git a/src/app/[locale]/projects/detail/[id]/components/ProjectDetailTab.tsx b/src/app/[locale]/projects/detail/[id]/components/ProjectDetailTab.tsx
index a5324ada7..a3297720a 100644
--- a/src/app/[locale]/projects/detail/[id]/components/ProjectDetailTab.tsx
+++ b/src/app/[locale]/projects/detail/[id]/components/ProjectDetailTab.tsx
@@ -9,18 +9,18 @@
'use client'
+import { AdministrationDataType, HttpStatus, SummaryDataType } from '@/object-types'
+import { ApiUtils } from '@/utils'
import { signOut, useSession } from 'next-auth/react'
import { useTranslations } from 'next-intl'
import { notFound } from 'next/navigation'
import { useEffect, useState } from 'react'
import { Button, Col, Dropdown, ListGroup, Row, Spinner, Tab } from 'react-bootstrap'
-
-import { AdministrationDataType, HttpStatus, SummaryDataType } from '@/object-types'
-import { ApiUtils } from '@/utils'
import LinkProjects from '../../../components/LinkProjects'
import Administration from './Administration'
import ChangeLog from './Changelog'
import EccDetails from './Ecc'
+import LicenseClearing from './LicenseClearing'
import Summary from './Summary'
export default function ViewProjects({ projectId }: { projectId: string }) {
@@ -145,7 +145,15 @@ export default function ViewProjects({ projectId }: { projectId: string }) {
)}
-
+
+ {summaryData && (
+
+ )}
+
diff --git a/src/styles/globals.css b/src/styles/globals.css
index a969dd011..5b6b9597c 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -684,7 +684,8 @@ th {
background-color: gray;
}
-.form-check-input[disabled] ~ .form-check-label, .form-check-input:disabled ~ .form-check-label {
+.form-check-input[disabled] ~ .form-check-label,
+.form-check-input:disabled ~ .form-check-label {
cursor: not-allowed !important;
}
@@ -720,3 +721,18 @@ th {
.toast-container {
position: fixed !important;
}
+
+.nav-pills .nav-link {
+ background-color: #f2f2f2 !important;
+ color: #f2a922 !important;
+}
+
+.nav-pills .nav-link:hover {
+ background-color: #f2f2f2 !important;
+ color: #21719c !important;
+}
+
+.nav-pills .nav-link.active {
+ background-color: #0c70f2 !important;
+ color: white !important;
+}
diff --git a/src/styles/gridjs/sw360.css b/src/styles/gridjs/sw360.css
index bb6397b16..b2b18ba2c 100644
--- a/src/styles/gridjs/sw360.css
+++ b/src/styles/gridjs/sw360.css
@@ -232,6 +232,7 @@ th.gridjs-th {
white-space: nowrap;
outline: none;
vertical-align: bottom;
+ white-space: break-spaces;
}
th.gridjs-th .gridjs-th-content {
text-overflow: ellipsis;
@@ -362,7 +363,6 @@ th.gridjs-th:last-child {
background-color: #9bc2f7;
}
-
.gridjs a:link {
text-decoration: none;
color: var(--sw360-primary-color);
@@ -373,8 +373,7 @@ th.gridjs-th:last-child {
color: var(--sw360-primary-color);
}
-
.gridjs a:hover {
text-decoration: underline;
color: var(--sw360-primary-background-color);
-}
\ No newline at end of file
+}