diff --git a/.github/workflows/code-checks.yml b/.github/workflows/code-checks.yml index b2063f8..39e11bd 100644 --- a/.github/workflows/code-checks.yml +++ b/.github/workflows/code-checks.yml @@ -26,7 +26,7 @@ jobs: npm run build working-directory: client env: - NODE_AUTH_TOKEN: ${{ secrets.GIT_HUB_PACKAGES_ACCESS_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.GIT_HUB_PACKAGES_ACCESS_TOKEN_WRITE }} - name: Set up JDK uses: actions/setup-java@v1 with: @@ -35,5 +35,5 @@ jobs: run: mvn clean install -s .mvn/settings.xml env: GIT_HUB_USERNAME: ${{ secrets.GIT_HUB_USERNAME }} - GIT_HUB_PACKAGES_ACCESS_TOKEN: ${{ secrets.GIT_HUB_PACKAGES_ACCESS_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.GIT_HUB_PACKAGES_ACCESS_TOKEN }} + GIT_HUB_PACKAGES_ACCESS_TOKEN: ${{ secrets.GIT_HUB_PACKAGES_ACCESS_TOKEN_WRITE }} + NODE_AUTH_TOKEN: ${{ secrets.GIT_HUB_PACKAGES_ACCESS_TOKEN_WRITE }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5424687..d91d17c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,15 +39,23 @@ jobs: run: | git config user.email "actions@dipa.online" git config user.name "DiPA GitHub Actions" + - name: Einstellen des Autorisierungstoken + run: | + npm config set //npm.pkg.github.com/:_authToken=${{ secrets.GIT_HUB_PACKAGES_ACCESS_TOKEN_WRITE }} + - name: Set up Node.js + uses: actions/setup-node@v2.1.5 + with: + node-version: 14.x + registry-url: "https://npm.pkg.github.com" + scope: "@dipa-projekt" - name: Release with Maven run: mvn -s .mvn/settings.xml -B -DreleaseVersion=${{ github.event.inputs.release_version }} gitflow:release --file pom.xml env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DOCKER_REGISTRY_USERNAME: dipa - DOCKER_REGISTRY_TOKEN: ${{ secrets.DOCKER_REGISTRY_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} GIT_HUB_USERNAME: ${{ secrets.GIT_HUB_USERNAME }} GIT_HUB_PACKAGES_ACCESS_TOKEN: ${{ secrets.GIT_HUB_PACKAGES_ACCESS_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.GIT_HUB_PACKAGES_ACCESS_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.GIT_HUB_PACKAGES_ACCESS_TOKEN }} - name: Deployment to Kubernetes run: | git checkout -f main @@ -63,5 +71,5 @@ jobs: DOCKER_REGISTRY_USERNAME: dipa DOCKER_REGISTRY_TOKEN: ${{ secrets.DOCKER_REGISTRY_TOKEN }} GIT_HUB_USERNAME: ${{ secrets.GIT_HUB_USERNAME }} - GIT_HUB_PACKAGES_ACCESS_TOKEN: ${{ secrets.GIT_HUB_PACKAGES_ACCESS_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.GIT_HUB_PACKAGES_ACCESS_TOKEN }} + GIT_HUB_PACKAGES_ACCESS_TOKEN: ${{ secrets.GIT_HUB_PACKAGES_ACCESS_TOKEN_WRITE }} + NODE_AUTH_TOKEN: ${{ secrets.GIT_HUB_PACKAGES_ACCESS_TOKEN_WRITE }} diff --git a/.gitignore b/.gitignore index bc15f83..f809966 100644 --- a/.gitignore +++ b/.gitignore @@ -197,3 +197,7 @@ buildNumber.properties .vscode /client/eslint-report.json /server/src/main/resources/liquibase-diff-changeLog.yaml + +OpenOfficePortable/ +client/node/ +node_modules/ diff --git a/.mvn/settings.xml b/.mvn/settings.xml index 10d4193..9c67b87 100644 --- a/.mvn/settings.xml +++ b/.mvn/settings.xml @@ -1,11 +1,48 @@ + + github-packages + + + + + github-packages + + + central + https://repo1.maven.org/maven2 + + true + + + true + + + + github-packages + GitHub - DiPA-Projekt + https://maven.pkg.github.com/dipa-projekt/projektassistent-openapi + + true + + + true + + + + + github - ${env.GITHUB_ACTOR} - ${env.GITHUB_TOKEN} + ${env.GIT_HUB_USERNAME} + ${env.GIT_HUB_PACKAGES_ACCESS_TOKEN} + + + github-packages + ${env.GIT_HUB_USERNAME} + ${env.GIT_HUB_PACKAGES_ACCESS_TOKEN} docker.dipa.online diff --git a/client/public/locales/de/translation.json b/client/public/locales/de/translation.json index 80e9f1b..8dc9e4b 100644 --- a/client/public/locales/de/translation.json +++ b/client/public/locales/de/translation.json @@ -6,7 +6,9 @@ }, "dataType": { "activity": "Aktivität", + "conventionFigure": "Konventionsabbildung", "decisionPoint": "Entscheidungspunkt", + "division": "Bereich", "externalTemplate": "Externe Kopiervorlage", "methodReference": "Methodenreferenz", "organisationRole": "Organisationsrolle", @@ -26,6 +28,7 @@ "SimpleDate": "{{date, date}}" }, "label": { + "activities": "Aktivitäten", "bibliography": "Literaturverzeichnis", "group": "Gruppe", "product": "Produkt", @@ -34,6 +37,7 @@ }, "text": { "insertTopicDescription": "Themenbeschreibungen einfügen", - "generateNavigation": "Erstelle Navigation" + "generateNavigation": "Erstelle Navigation", + "pleaseSelectSubChapter": "Bitte wählen Sie ein Unterkapitel." } } diff --git a/client/public/style.css b/client/public/style.css index 2b21cb9..5d18aa2 100644 --- a/client/public/style.css +++ b/client/public/style.css @@ -141,6 +141,15 @@ ul.sideMenu > li > ul > li > ul > li > div { color: rgba(0, 0, 0, 0.88); } -.disable-link{ +.disable-link { pointer-events: none; } + +/* side background color like menu background color */ +.ant-layout-sider-children { + background: #f5f5f5; +} + +.ant-layout-header { + background: #fff; +} diff --git a/client/src/assets/img/fc3fc9d51ffd43/ALLG-Logo-Farbe.gif b/client/src/assets/img/fc3fc9d51ffd43/ALLG-Logo-Farbe.gif new file mode 100644 index 0000000..ac7c3c7 Binary files /dev/null and b/client/src/assets/img/fc3fc9d51ffd43/ALLG-Logo-Farbe.gif differ diff --git a/client/src/components/Breadcrumbs.tsx b/client/src/components/Breadcrumbs.tsx index 409715a..454559f 100644 --- a/client/src/components/Breadcrumbs.tsx +++ b/client/src/components/Breadcrumbs.tsx @@ -2,7 +2,7 @@ import { Link, useLocation } from 'react-router-dom'; import React, { useEffect, useState } from 'react'; import { useDocumentation } from '../context/DocumentationContext'; import { Breadcrumb } from 'antd'; -import { getSearchStringFromHash } from '../shares/utils'; +import { decodeXml, getSearchStringFromHash } from '../shares/utils'; export function Breadcrumbs() { // const [pathSnippets, setPathSnippets] = useState([]); @@ -49,7 +49,7 @@ export function Breadcrumbs() { const url = '/documentation/' + parent?.key + getSearchStringFromHash(); return { key: url, - title: {parent?.label}, + title: {decodeXml(parent?.label)}, }; }) .reverse(); diff --git a/client/src/components/header/SiteHeader.tsx b/client/src/components/header/SiteHeader.tsx index d1219a7..3698c74 100644 --- a/client/src/components/header/SiteHeader.tsx +++ b/client/src/components/header/SiteHeader.tsx @@ -1,32 +1,26 @@ import { Col, Layout, Menu, MenuProps, Row } from 'antd'; -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { BookOutlined, FileTextOutlined, HomeOutlined, ScissorOutlined } from '@ant-design/icons'; -import { Link } from 'react-router-dom'; +import { Link, useLocation } from 'react-router-dom'; import { useTailoring } from '../../context/TailoringContext'; import useImage from '../../hooks/useImage'; import { LinkWithQuery } from '../LinkWithQuery'; const { Header } = Layout; -// -// -// Home -// -// -// -// Tailoring -// -// -// -// Dokumentation -// -// -// -// Produktvorlagen -// - export const SiteHeader = (props: any) => { const { tailoringParameter } = useTailoring(); + const location = useLocation(); + const [current, setCurrent] = useState(location.pathname); + + useEffect(() => { + if (location) { + const splitPathname = location.pathname.split('/'); + if (splitPathname.length >= 2 && current !== splitPathname[1]) { + setCurrent(splitPathname[1]); + } + } + }, [location]); const items: MenuProps['items'] = [ { @@ -44,7 +38,7 @@ export const SiteHeader = (props: any) => { Dokumentation ), - key: 'dokumentation', + key: 'documentation', icon: , disabled: !tailoringParameter.projectTypeId, }, @@ -112,8 +106,8 @@ export const SiteHeader = (props: any) => { diff --git a/client/src/components/projekthandbuch/documentation/Documentation.tsx b/client/src/components/projekthandbuch/documentation/Documentation.tsx index 89a0f96..1dec2d8 100644 --- a/client/src/components/projekthandbuch/documentation/Documentation.tsx +++ b/client/src/components/projekthandbuch/documentation/Documentation.tsx @@ -31,6 +31,7 @@ export type PageEntry = { export type TableEntry = { id: string; descriptionEntry: string; + dataEntryDescription?: string; dataEntries: DataEntry[]; }; diff --git a/client/src/components/projekthandbuch/documentation/content/Content.tsx b/client/src/components/projekthandbuch/documentation/content/Content.tsx index 9ff1850..affa31f 100644 --- a/client/src/components/projekthandbuch/documentation/content/Content.tsx +++ b/client/src/components/projekthandbuch/documentation/content/Content.tsx @@ -4,15 +4,17 @@ import parse, { domToReact } from 'html-react-parser'; import { Anchor, Col, FloatButton, Layout, Row, Spin, Tag } from 'antd'; import React, { useEffect, useState } from 'react'; // import { DataEntry, PageEntry, TableEntry } from '@dipa-projekt/projektassistent-openapi'; -import { Link } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import { useDocumentation } from '../../../../context/DocumentationContext'; import { decodeXml, fixLinksInText, flatten, + getFigureDesignationFromText, getJsonDataFromXml, getMenuItemByAttributeValue, getSearchStringFromHash, + replaceImageUrlInText, replaceUrlInText, } from '../../../../shares/utils'; import { AnchorLinkItemProps } from 'antd/es/anchor/Anchor'; @@ -26,6 +28,18 @@ import { PageEntryContent } from './PageEntryContent'; import { weitApiUrl } from '../../../app/App'; import { HashLink } from 'react-router-hash-link'; import { DataEntry, PageEntry, TableEntry } from '../Documentation'; +import { ProjectType } from '../../projekt/project'; + +function getConceptMappingData(conceptMapping: XMLElement, tagName: string, suffix: string): DataEntry[] { + return conceptMapping.getElementsByTagName(tagName).map((data: XMLElement) => { + const dataEntry: DataEntry = { + id: tagName !== 'wird_abgebildet_durchThemaRef' ? data.attributes.id : undefined, + title: data.attributes?.name || data.attributes?.titel, + suffix: suffix, + }; + return dataEntry; + }); +} export function Content() { const [loading, setLoading] = useState(false); @@ -48,7 +62,8 @@ export function Content() { contentProductDependencyId, roleId, decisionPointId, - processModuleId, + conventionFigureId, + divisionId, methodReferenceId, toolReferenceId, processBuildingBlockId, @@ -59,10 +74,14 @@ export function Content() { activityId, templateDisciplineId, productDisciplineId, + glossaryEntryId, entryId, selectedIndexType, + setSelectedItemKey, } = useDocumentation(); + const navigate = useNavigate(); + useEffect(() => { if (selectedPageEntry?.subPageEntries) { console.log('selectedPageEntry?.subPageEntries set'); @@ -83,14 +102,11 @@ export function Content() { }, [productId]); useEffect(() => { - async function mount() { - if (productDisciplineId) { - const content = await getProductDisciplineContent(); - setSelectedPageEntry(content); - } + // redirect only if discipline was selected directly + if (productDisciplineId && !productId) { + redirectToFirstChildWithContent(productDisciplineId); } - void mount().then(); //eslint-disable-next-line }, [productDisciplineId]); @@ -125,24 +141,25 @@ export function Content() { let content; switch (selectedIndexType) { case IndexTypeEnum.PRODUCT: - content = await getProductIndexContent(); + content = getProductIndexContent(); break; case IndexTypeEnum.ROLE: - content = await getRoleIndexContent(); + content = getRoleIndexContent(); break; case IndexTypeEnum.PROCESS: - content = await getProcessIndexContent(); + content = getProcessIndexContent(); break; case IndexTypeEnum.TAILORING: - content = await getTailoringIndexContent(); + content = getTailoringIndexContent(); break; case IndexTypeEnum.WORK_AIDS: - content = await getWorkAidsIndexContent(); + content = getWorkAidsIndexContent(); + break; + case IndexTypeEnum.OTHER_STANDARDS: + content = getOtherStandardsIndexContent(); break; } setSelectedPageEntry(content); - } else { - console.log('no selectedIndexType'); } } @@ -188,15 +205,27 @@ export function Content() { useEffect(() => { async function mount() { - if (processModuleId) { - const content = await getProcessModuleContent(); + if (conventionFigureId) { + const content = await getConventionFigureContent(); + setSelectedPageEntry(content); + } + } + + void mount().then(); + //eslint-disable-next-line + }, [conventionFigureId]); + + useEffect(() => { + async function mount() { + if (divisionId) { + const content = await getDivisionContent(); setSelectedPageEntry(content); } } void mount().then(); //eslint-disable-next-line - }, [processModuleId]); + }, [divisionId]); useEffect(() => { async function mount() { @@ -294,6 +323,18 @@ export function Content() { //eslint-disable-next-line }, [templateDisciplineId]); + useEffect(() => { + async function mount() { + if (glossaryEntryId) { + const content = await getGlossaryEntryContent(); + setSelectedPageEntry(content); + } + } + + void mount().then(); + //eslint-disable-next-line + }, [glossaryEntryId]); + useEffect(() => { async function mount() { if (entryId) { @@ -498,6 +539,11 @@ export function Content() { const childText = jsonDataFromXml.children.find((child) => child.name === 'Text'); if (childText) { textPart = decodeXml(childText.value); + } else if (jsonDataFromXml.children.length > 0) { + // redirect to first child with content + redirectToFirstChildWithContent(sectionId); + } else { + textPart = t('text.pleaseSelectSubChapter'); } } @@ -511,38 +557,6 @@ export function Content() { }; } - async function getProductDisciplineContent(): Promise { - const disciplineId = productDisciplineId?.replace('productDiscipline_', ''); - - const projectTypeUrl = - weitApiUrl + - '/Tailoring/V-Modellmetamodell/mm_2021/V-Modellvariante/' + - tailoringParameter.modelVariantId + - '/Projekttyp/' + - tailoringParameter.projectTypeId + - '/Projekttypvariante/' + - tailoringParameter.projectTypeVariantId + - '/Disziplin/' + - disciplineId + - '?' + - getProjectFeaturesString(); - - const jsonDataFromXml = await getJsonDataFromXml(projectTypeUrl); - - const tableEntries: TableEntry[] = []; - - ////////////////////////////////////////////// - - return { - id: jsonDataFromXml.attributes.id, - // menuEntryId: jsonDataFromXml.attributes.id, - header: jsonDataFromXml.attributes.name, - descriptionText: '', - tableEntries: tableEntries, - // subPageEntries: subPageEntries, - }; - } - async function getDisciplineContent(): Promise { const projectTypeUrl = weitApiUrl + @@ -742,7 +756,10 @@ export function Content() { } ///////////////////////////// - const tools = [...activities, ...products, ...activitiesToTools]; + // To hide the links to the activities in the tools section of a product the id has to removed here + const activitiesWithoutId = activities.map(({ id, ...keepAttrs }) => keepAttrs); + + const tools = [...activitiesWithoutId, ...products, ...activitiesToTools]; if (tools.length > 0) { tableEntries.push({ @@ -1172,12 +1189,12 @@ export function Content() { }; } - async function getProductIndexContent(): Promise { + function getProductIndexContent(): PageEntry { const filterRelevantDataTypes = flatten(navigationData).filter((item: any) => [NavTypeEnum.PRODUCT].includes(item.dataType) ); - let data: any[] = filterRelevantDataTypes.map((navItem: any): any => { + const data: any[] = filterRelevantDataTypes.map((navItem: any): any => { return { modelElement: { id: navItem.key, @@ -1189,51 +1206,6 @@ export function Content() { ///////////////// - const filterDisciplineDataTypes = flatten(navigationData).filter((item: any) => - [NavTypeEnum.DISCIPLINE].includes(item.dataType) - ); - - for (const discipline of filterDisciplineDataTypes) { - const productsUrl = - weitApiUrl + - '/Tailoring/V-Modellmetamodell/mm_2021/V-Modellvariante/' + - tailoringParameter.modelVariantId + - '/Projekttyp/' + - tailoringParameter.projectTypeId + - '/Projekttypvariante/' + - tailoringParameter.projectTypeVariantId + - '/Disziplin/' + - discipline.key + - '/Produkt?' + - getProjectFeaturesString(); - - const jsonDataFromXml = await getJsonDataFromXml(productsUrl); - - let productTopicEntries: any[] = []; - - if (jsonDataFromXml.children) { - for (const product of jsonDataFromXml.children) { - const themaRef: XMLElement[] = product.getElementsByTagName('ThemaRef'); - productTopicEntries = [ - ...productTopicEntries, - ...themaRef.map((subjectRef) => { - return { - modelElement: { - id: product.attributes.id + '#' + subjectRef.attributes.id, - text: subjectRef.attributes.name, - }, - dataTypes: [NavTypeEnum.TOPIC], - }; - }), - ]; - } - } - - data = [...data, ...productTopicEntries]; - } - - ///////////////// - const columns: ColumnsType = [ { title: 'Modellelement', @@ -1264,9 +1236,6 @@ export function Content() { if (tag === 'product') { color = 'geekblue'; } - if (tag === 'topic') { - color = 'green'; - } return ( {t('translation:dataType.' + tag)} @@ -1290,7 +1259,7 @@ export function Content() { }; } - async function getRoleIndexContent(): Promise { + function getRoleIndexContent(): PageEntry { const filterRelevantDataTypes = flatten(navigationData).filter((item: any) => [NavTypeEnum.PROJECT_TEAM_ROLE, NavTypeEnum.PROJECT_ROLE, NavTypeEnum.ORGANISATION_ROLE].includes(item.dataType) ); @@ -1361,11 +1330,9 @@ export function Content() { }; } - async function getProcessIndexContent(): Promise { + function getProcessIndexContent(): PageEntry { const filterRelevantDataTypes = flatten(navigationData).filter((item: any) => - [NavTypeEnum.PROCESS_MODULE, NavTypeEnum.DECISION_POINT, NavTypeEnum.PROJECT_TYPE_VARIANT_SEQUENCE].includes( - item.dataType - ) + [NavTypeEnum.DECISION_POINT, NavTypeEnum.PROJECT_TYPE_VARIANT_SEQUENCE].includes(item.dataType) ); const data: any[] = filterRelevantDataTypes.map((navItem: any): any => { @@ -1435,7 +1402,7 @@ export function Content() { }; } - async function getTailoringIndexContent(): Promise { + function getTailoringIndexContent(): PageEntry { const filterRelevantDataTypes = flatten(navigationData).filter((item: any) => [ NavTypeEnum.PROJECT_TYPE_VARIANT, @@ -1447,7 +1414,7 @@ export function Content() { const data: any[] = filterRelevantDataTypes.map((navItem: any): any => { return { - modelElement: navItem.label, + modelElement: { id: navItem.key, text: navItem.label }, dataTypes: [navItem.dataType], }; }); @@ -1459,9 +1426,9 @@ export function Content() { key: 'modelElement', defaultSortOrder: 'ascend', sorter: { - compare: (a, b) => sorter(a.modelElement, b.modelElement), + compare: (a, b) => sorter(a.modelElement.text, b.modelElement.text), }, - render: (text: string) => {text}, // TODO + render: (object) => {object.text}, }, { title: 'Typ', @@ -1514,7 +1481,7 @@ export function Content() { }; } - async function getWorkAidsIndexContent(): Promise { + function getWorkAidsIndexContent(): PageEntry { const filterRelevantDataTypes = flatten(navigationData).filter((item: any) => [NavTypeEnum.ACTIVITY, NavTypeEnum.METHOD_REFERENCE, NavTypeEnum.TOOL_REFERENCE].includes(item.dataType) ); @@ -1522,7 +1489,7 @@ export function Content() { const data: any[] = filterRelevantDataTypes.map((navItem: any): any => { return { - modelElement: navItem.label, + modelElement: { id: navItem.key, text: navItem.label }, dataTypes: [navItem.dataType], }; }); @@ -1534,9 +1501,9 @@ export function Content() { key: 'modelElement', defaultSortOrder: 'ascend', sorter: { - compare: (a, b) => sorter(a.modelElement, b.modelElement), + compare: (a, b) => sorter(a.modelElement.text, b.modelElement.text), }, - render: (text: string) => {text}, // TODO + render: (object) => {object.text}, }, { title: 'Typ', @@ -1592,6 +1559,73 @@ export function Content() { }; } + function getOtherStandardsIndexContent(): PageEntry { + const filterRelevantDataTypes = flatten(navigationData).filter((item: any) => + [NavTypeEnum.CONVENTION_FIGURE, NavTypeEnum.DIVISION].includes(item.dataType) + ); + + const data: any[] = filterRelevantDataTypes.map((navItem: any): any => { + return { + modelElement: { id: navItem.key, text: navItem.label }, + dataTypes: [navItem.dataType], + }; + }); + + const columns: ColumnsType = [ + { + title: 'Modellelement', + dataIndex: 'modelElement', + key: 'modelElement', + defaultSortOrder: 'ascend', + sorter: { + compare: (a, b) => sorter(a.modelElement.text, b.modelElement.text), + }, + render: (object) => {object.text}, + }, + { + title: 'Typ', + dataIndex: 'dataTypes', + key: 'dataTypes', + filters: [...new Set(data.map((item) => item.dataTypes[0]))].map((item) => ({ + text: t('translation:dataType.' + item), + value: item, + })), + onFilter: (value: string | number | boolean, record: any) => record.dataTypes.indexOf(value) === 0, + sorter: { + compare: (a, b) => sorter(a.dataTypes[0], b.dataTypes[0]), + }, + render: (tags: string[]) => ( + + {tags?.map((tag) => { + let color; + if (tag === 'conventionFigure') { + color = 'geekblue'; + } + if (tag === 'division') { + color = 'green'; + } + return ( + + {t('translation:dataType.' + tag)} + + ); + })} + + ), + }, + ]; + + return { + id: 'otherStandardsIndexContent', //jsonDataFromXml.attributes.id, + // menuEntryId: jsonDataFromXml.attributes.id, + header: 'Andere-Standards-Index', //jsonDataFromXml.attributes.name, + descriptionText: '', + tableEntries: [], + dataSource: data, + columns: columns, + }; + } + async function getDecisionPointContent(): Promise { const tailoringProcessBuildingBlocksUrl = weitApiUrl + @@ -1647,39 +1681,153 @@ export function Content() { }; } - async function getProcessModuleContent(): Promise { - const processModuleUrl = + async function getConventionFigureContent(): Promise { + const conventionFigureUrl = weitApiUrl + - '/Tailoring/V-Modellmetamodell/mm_2021/V-Modellvariante/' + + '/V-Modellmetamodell/mm_2021/V-Modellvariante/' + tailoringParameter.modelVariantId + - '/Projekttyp/' + - tailoringParameter.projectTypeId + - '/Projekttypvariante/' + - tailoringParameter.projectTypeVariantId + - '/Ablaufbaustein/' + - processModuleId + - '?' + - getProjectFeaturesString(); + '/Konventionsabbildung/' + + conventionFigureId; - // let idCounter = 2000; + const jsonDataFromXml = await getJsonDataFromXml(conventionFigureUrl); - const jsonDataFromXml = await getJsonDataFromXml(processModuleUrl); - - const description = decodeXml(jsonDataFromXml.getElementsByTagName('Beschreibung')[0]?.value); + const summary = decodeXml(jsonDataFromXml.getElementsByTagName('Zusammenfassung')[0]?.value); const tableEntries: TableEntry[] = []; - ////////////////////////////////////////////// - return { id: jsonDataFromXml.attributes.id, - // menuEntryId: jsonDataFromXml.attributes.id, header: jsonDataFromXml.attributes.name, - descriptionText: description, + descriptionText: replaceImageUrlInText(summary, tailoringParameter), tableEntries: tableEntries, - // subPageEntries: subPageEntries, }; } + + async function getDivisionContent(): Promise { + if (divisionId) { + const foundDivision = getMenuItemByAttributeValue(navigationData, 'key', divisionId); + + const conventionFigureId = foundDivision?.parent?.key; + + const conventionFigureUrl = + weitApiUrl + + '/V-Modellmetamodell/mm_2021/V-Modellvariante/' + + tailoringParameter.modelVariantId + + '/Konventionsabbildung/' + + conventionFigureId; + + let idCounter = 2000; + + const jsonDataFromXml = await getJsonDataFromXml(conventionFigureUrl); + + const divisionData = jsonDataFromXml + .getElementsByTagName('Bereich') + .find((division) => division.attributes.id === divisionId); + + if (!divisionData) { + return null; + } + + const explanation = decodeXml(divisionData.getElementsByTagName('Erläuterung')[0]?.value); + const conceptMappings = divisionData.getElementsByTagName('Begriffsabbildung'); + + const tableEntries: TableEntry[] = []; + + conceptMappings.forEach((conceptMapping) => { + const conceptMappingName = conceptMapping.attributes.name; + + const description = decodeXml(conceptMapping.getElementsByTagName('Beschreibung')[0]?.value); + + let dataEntries: DataEntry[] = []; + + const wird_abgebildet_durchKapitelRefs = getConceptMappingData( + conceptMapping, + 'wird_abgebildet_durchKapitelRef', + '(Kapitel)' + ); + const wird_abgebildet_durchVBRefs = getConceptMappingData( + conceptMapping, + 'wird_abgebildet_durchVBRef', + '(Vorgehensbaustein)' + ); + const wird_abgebildet_durchPTVRefs = getConceptMappingData( + conceptMapping, + 'wird_abgebildet_durchPTVRef', + '(Projekttypvariante)' + ); + const wird_abgebildet_durchRolleRefs = getConceptMappingData( + conceptMapping, + 'wird_abgebildet_durchRolleRef', + '(Rolle)' + ); + const wird_abgebildet_durchAktivitaetRefs = getConceptMappingData( + conceptMapping, + 'wird_abgebildet_durchAktivitätRef', + '(Aktivität)' + ); + const wird_abgebildet_durchDisziplinRefs = getConceptMappingData( + conceptMapping, + 'wird_abgebildet_durchDisziplinRef', + '(Disziplin)' + ); + const wird_abgebildet_durchProduktRefs = getConceptMappingData( + conceptMapping, + 'wird_abgebildet_durchProduktRef', + '(Produkt)' + ); + + // TODO: Hier gibt's noch Probleme bei der Verlinkung, weil Themen keine eigene Seite haben, sondern nur auf + // Produkten angezeigt werden. + const wird_abgebildet_durchThemaRefs = getConceptMappingData( + conceptMapping, + 'wird_abgebildet_durchThemaRef', + '(Thema)' + ); + + dataEntries = [ + ...dataEntries, + ...wird_abgebildet_durchKapitelRefs, + ...wird_abgebildet_durchVBRefs, + ...wird_abgebildet_durchPTVRefs, + ...wird_abgebildet_durchRolleRefs, + ...wird_abgebildet_durchAktivitaetRefs, + ...wird_abgebildet_durchDisziplinRefs, + ...wird_abgebildet_durchProduktRefs, + ...wird_abgebildet_durchThemaRefs, + ]; + + const isRepresentedByData = []; + + if (dataEntries.length > 0) { + isRepresentedByData.push([ + { + subheader: { + id: idCounter + '_isRepresentedBy', + title: 'Wird erfüllt durch:', + isLink: false, + }, + dataEntryDescription: description, + dataEntries: dataEntries, + }, + ]); + } + + tableEntries.push({ + id: (idCounter++).toString(), + descriptionEntry: conceptMappingName, + dataEntries: isRepresentedByData, + }); + }); + + return { + id: divisionData.attributes.id, + header: decodeXml(divisionData.attributes.name), + descriptionText: explanation, + tableEntries: tableEntries, + }; + } + } + async function getContentProductDependencyContent(): Promise { const contentProductDependenciesUrl = weitApiUrl + @@ -1833,6 +1981,30 @@ export function Content() { return jsonDataFromXml.getElementsByTagName('Aktivität'); } + async function getGlossaryEntryContent(): Promise { + const expressionUrl = + weitApiUrl + + '/V-Modellmetamodell/mm_2021/V-Modellvariante/' + + tailoringParameter.modelVariantId + + '/Begriff/' + + glossaryEntryId; + + const jsonDataFromXml = await getJsonDataFromXml(expressionUrl); + + const description = decodeXml(jsonDataFromXml.getElementsByTagName('Erläuterung')[0]?.value); + + const tableEntries: TableEntry[] = []; + + ////////////////////////////////////////////// + + return { + id: jsonDataFromXml.attributes.id, + header: jsonDataFromXml.attributes.name, + descriptionText: description, + tableEntries: tableEntries, + }; + } + async function getTemplatesContent(): Promise { const disciplineId = templateDisciplineId?.replace('td_', ''); // get all products with externalTemplate info for templateDisciplineId @@ -1877,7 +2049,6 @@ export function Content() { const jsonDataFromXml = await getJsonDataFromXml(externalTemplateUrl); - const templateId = jsonDataFromXml.attributes.id; const templateName = jsonDataFromXml.attributes.name; const templateUri = jsonDataFromXml.getElementsByTagName('URI')[0]?.value; @@ -1982,16 +2153,42 @@ export function Content() { ////////////////////////////////////////////// const activitiesData = await getActivitiesData(); - const activitiesToTools = []; + const activitiesToTools: DataEntry[] = []; + const activitiesToProducts: DataEntry[] = []; for (const activity of activitiesData) { const methodReferences = activity.getElementsByTagName('MethodenreferenzRef'); for (const methodReference of methodReferences) { if (methodReference.attributes.id === methodReferenceId) { - activitiesToTools.push({ - id: activity.attributes.id, - title: activity.attributes.name, - }); + /* The method and tool references (reference work aids) contain links to the corresponding activities. + As the activities are no longer part of the documentation (since version 2.4), the links cannot be resolved. + Instead of the activity, a link should therefore be created to the product linked to the activity. + + -> So, if the activity id is not in navigation menu change linked site to products page of the activity. + */ + + const foundActivity = getMenuItemByAttributeValue(navigationData, 'key', activity.attributes.id); + + if (foundActivity) { + activitiesToTools.push({ + id: activity.attributes.id, + title: activity.attributes.name, + }); + } else { + const products = activity.getElementsByTagName('ProduktRef'); + + for (const product of products) { + const foundProduct = getMenuItemByAttributeValue(navigationData, 'key', product.attributes.id); + + if (foundProduct) { + activitiesToProducts.push({ + id: product.attributes.id, + title: activity.attributes.name, + suffix: '(' + product.attributes.name + ')', + }); + } + } + } } } } @@ -1999,13 +2196,19 @@ export function Content() { if (activitiesToTools.length > 0) { tableEntries.push({ id: (idCounter++).toString(), - descriptionEntry: 'Aktivitäten', + descriptionEntry: t('translation:label.activities'), dataEntries: activitiesToTools, }); + } else if (activitiesToProducts.length > 0) { + tableEntries.push({ + id: (idCounter++).toString(), + descriptionEntry: t('translation:label.products'), + dataEntries: activitiesToProducts, + }); } // ////////////////////////////////////////////// - // + const quellen = quelleRefs.flatMap((entry: XMLElement) => { return entry.getElementsByTagName('QuelleRef').map((productRef) => { return { @@ -2053,16 +2256,42 @@ export function Content() { ////////////////////////////////////////////// const activitiesData = await getActivitiesData(); - const activitiesToTools = []; + const activitiesToTools: DataEntry[] = []; + const activitiesToProducts: DataEntry[] = []; for (const activity of activitiesData) { const toolReferences = activity.getElementsByTagName('WerkzeugreferenzRef'); for (const toolReference of toolReferences) { if (toolReference.attributes.id === toolReferenceId) { - activitiesToTools.push({ - id: activity.attributes.id, - title: activity.attributes.name, - }); + /* The method and tool references (reference work aids) contain links to the corresponding activities. + As the activities are no longer part of the documentation (since version 2.4), the links cannot be resolved. + Instead of the activity, a link should therefore be created to the product linked to the activity. + + -> So, if the activity id is not in navigation menu change linked site to products page of the activity. + */ + + const foundActivity = getMenuItemByAttributeValue(navigationData, 'key', activity.attributes.id); + + if (foundActivity) { + activitiesToTools.push({ + id: activity.attributes.id, + title: activity.attributes.name, + }); + } else { + const products = activity.getElementsByTagName('ProduktRef'); + + for (const product of products) { + const foundProduct = getMenuItemByAttributeValue(navigationData, 'key', product.attributes.id); + + if (foundProduct) { + activitiesToProducts.push({ + id: product.attributes.id, + title: activity.attributes.name, + suffix: '(' + product.attributes.name + ')', + }); + } + } + } } } } @@ -2070,9 +2299,15 @@ export function Content() { if (activitiesToTools.length > 0) { tableEntries.push({ id: (idCounter++).toString(), - descriptionEntry: 'Aktivitäten', + descriptionEntry: t('translation:label.activities'), dataEntries: activitiesToTools, }); + } else if (activitiesToProducts.length > 0) { + tableEntries.push({ + id: (idCounter++).toString(), + descriptionEntry: t('translation:label.products'), + dataEntries: activitiesToProducts, + }); } ////////////////////////////////////////////// @@ -2456,27 +2691,29 @@ export function Content() { let idCounter = 2000; - const sequence = decodeXml(jsonDataFromXml.getElementsByTagName('Ablauf')[0]?.value); - const ablaufbausteinRefs: XMLElement[] = jsonDataFromXml.getElementsByTagName('AblaufbausteinRef'); - - const ablaufbausteine = ablaufbausteinRefs.map((ablaufbausteinRef) => { - return { - id: ablaufbausteinRef.attributes.id, - title: ablaufbausteinRef.attributes.name, - }; - }); + const title = decodeXml(jsonDataFromXml.attributes.name); + const projectType: ProjectType = jsonDataFromXml.getElementsByTagName('ProjekttypRef')[0] + ?.attributes as ProjectType; - const tableEntries: TableEntry[] = []; + const sequence = decodeXml(jsonDataFromXml.getElementsByTagName('Ablauf')[0]?.value); + const figureDesignation = getFigureDesignationFromText(sequence); - if (ablaufbausteine.length > 0) { - tableEntries.push({ - id: (idCounter++).toString(), - descriptionEntry: 'Ablaufbausteine', - dataEntries: ablaufbausteine, - }); - } + const getFigureUrl = + weitApiUrl + + '/Tailoring/V-Modellmetamodell/mm_2021/V-Modellvariante/' + + tailoringParameter.modelVariantId + + '/Projekttyp/' + + projectType.id + + '/Projekttypvariante/' + + projectTypeVariantId + + '/Grafik/Abb:' + + figureDesignation + + '?' + + getProjectFeaturesString(); - // AblaufbausteinRef; + const imageTag = '

'; + const imageDescriptionTag = + '

' + 'Abbildung [' + figureDesignation + ']: ' + title + '

'; ////////////////////////////////////////////// @@ -2484,8 +2721,9 @@ export function Content() { id: jsonDataFromXml.attributes.id, // menuEntryId: jsonDataFromXml.attributes.id, header: jsonDataFromXml.attributes.name, - descriptionText: replaceUrlInText(sequence, tailoringParameter, getProjectFeaturesString()), - tableEntries: tableEntries, + descriptionText: + replaceUrlInText(sequence, tailoringParameter, getProjectFeaturesString()) + imageTag + imageDescriptionTag, + tableEntries: [], // subPageEntries: subPageEntries, }; } @@ -2509,7 +2747,7 @@ export function Content() { let idCounter = 2000; const sinnUndZweck = decodeXml(jsonDataFromXml.getElementsByTagName('Sinn_und_Zweck')[0]?.value); - const aktivityRef: XMLElement[] = jsonDataFromXml.getElementsByTagName('AktivitätZuProduktRef'); + const activityRef: XMLElement[] = jsonDataFromXml.getElementsByTagName('AktivitätZuProduktRef'); const methodsRef: XMLElement[] = jsonDataFromXml.getElementsByTagName('AktivitätZuMethodenreferenzRef'); const toolsRef: XMLElement[] = jsonDataFromXml.getElementsByTagName('AktivitätZuWerkzeugreferenzRef'); // const description = decodeXml(jsonDataFromXml.getElementsByTagName('Beschreibung')[0]?.value); @@ -2518,7 +2756,7 @@ export function Content() { ////////////////////////////////////////////// - const products = aktivityRef.flatMap((entry) => { + const products = activityRef.flatMap((entry) => { return entry.getElementsByTagName('ProduktRef').map((productRef) => { return { id: productRef.attributes.id, @@ -2580,6 +2818,21 @@ export function Content() { }; } + function redirectToFirstChildWithContent(sectionId: string) { + const currentMenuItem = getMenuItemByAttributeValue(navigationData, 'key', sectionId); + const currentChildren = currentMenuItem?.children; + + if (currentChildren?.length > 0) { + const childId = currentChildren[0].key; + + if (childId) { + setSelectedItemKey(childId); + + navigate(`/documentation/${childId}${getSearchStringFromHash()}`); + } + } + } + return ( <> diff --git a/client/src/components/projekthandbuch/documentation/content/DataTable.tsx b/client/src/components/projekthandbuch/documentation/content/DataTable.tsx index d26cd9e..7198781 100644 --- a/client/src/components/projekthandbuch/documentation/content/DataTable.tsx +++ b/client/src/components/projekthandbuch/documentation/content/DataTable.tsx @@ -50,6 +50,7 @@ icons.set('Ausgewählte Vorgehensbausteine', { color: '#689fd0', icon: }); // Referenz Arbeitshilfen icons.set('Produkt', { color: '#689fd0', icon: }); +icons.set('Produkte', { color: '#689fd0', icon: }); icons.set('Werkzeuge', { color: '#689fd0', icon: }); icons.set('Arbeitsschritte', { color: '#689fd0', icon: }); icons.set('Methoden', { color: '#689fd0', icon: }); diff --git a/client/src/components/projekthandbuch/documentation/content/TableEntriesList.tsx b/client/src/components/projekthandbuch/documentation/content/TableEntriesList.tsx index baab4cb..13630bf 100644 --- a/client/src/components/projekthandbuch/documentation/content/TableEntriesList.tsx +++ b/client/src/components/projekthandbuch/documentation/content/TableEntriesList.tsx @@ -8,9 +8,15 @@ import { DataEntry } from '../Documentation'; export function TableEntriesList(props: { inputData: DataEntry[] }) { const entries: JSX.Element[] = []; - props.inputData.map((entryItem: DataEntry) => { + props.inputData.map((entryItem: DataEntry, entryItemIndex: number) => { if (Array.isArray(entryItem)) { for (const entrySubItem of entryItem) { + entries.push( +
+ {entrySubItem?.dataEntryDescription ?
{parse(entrySubItem.dataEntryDescription)}
: ''} +
+ ); + entries.push( {entrySubItem.subheader?.isLink ? ( @@ -23,7 +29,7 @@ export function TableEntriesList(props: { inputData: DataEntry[] }) { ); - entrySubItem.dataEntries.map((innerEntryItem: DataEntry) => { + entrySubItem.dataEntries.map((innerEntryItem: DataEntry, innerEntryItemIndex: number) => { if (innerEntryItem?.id) { entries.push( @@ -34,20 +40,30 @@ export function TableEntriesList(props: { inputData: DataEntry[] }) { ); } else { - entries.push({parse(fixLinksInText(innerEntryItem.title))}); + entries.push( + + {parse(fixLinksInText(innerEntryItem.title))} + {innerEntryItem.suffix && {innerEntryItem.suffix}} + + ); } }); } } else { if (entryItem?.id) { entries.push( - + {entryItem.title} {entryItem.suffix && {entryItem.suffix}} ); } else { - entries.push({parse(fixLinksInText(entryItem.title))}); + entries.push( + + {parse(fixLinksInText(entryItem.title))} + {entryItem.suffix && {entryItem.suffix}} + + ); } } }); diff --git a/client/src/components/projekthandbuch/documentation/navigation/Navigation.tsx b/client/src/components/projekthandbuch/documentation/navigation/Navigation.tsx index 3cd5a67..5f7786c 100644 --- a/client/src/components/projekthandbuch/documentation/navigation/Navigation.tsx +++ b/client/src/components/projekthandbuch/documentation/navigation/Navigation.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; -import { useLocation, useNavigate } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { AuditOutlined, @@ -9,13 +9,19 @@ import { HomeOutlined, OrderedListOutlined, PaperClipOutlined, + ReadOutlined, ScissorOutlined, ShoppingOutlined, TeamOutlined, ToolOutlined, } from '@ant-design/icons'; import { Layout, Menu, Spin } from 'antd'; -import { getJsonDataFromXml, getMenuItemByAttributeValue, getSearchStringFromHash } from '../../../../shares/utils'; +import { + decodeXml, + getJsonDataFromXml, + getMenuItemByAttributeValue, + getSearchStringFromHash, +} from '../../../../shares/utils'; // import { MenuEntry } from '@dipa-projekt/projektassistent-openapi'; import { useDocumentation } from '../../../../context/DocumentationContext'; @@ -37,7 +43,6 @@ export enum NavTypeEnum { PROJECT_TEAM_ROLE = 'projectTeamRole', ORGANISATION_ROLE = 'organisationRole', DECISION_POINT = 'decisionPoint', - PROCESS_MODULE = 'processModule', PROCESS_BUILDING_BLOCK = 'processBuildingBlock', ACTIVITY = 'activity', ACTIVITY_DISCIPLINE = 'activityDiscipline', @@ -50,6 +55,9 @@ export enum NavTypeEnum { TEMPLATE_DISCIPLINE = 'templateDiscipline', PRODUCT_DISCIPLINE = 'productDiscipline', SAMPLE_TEXT = 'sampleText', + GLOSSARY_ENTRY = 'glossaryEntry', + CONVENTION_FIGURE = 'conventionFigure', + DIVISION = 'division', } export enum IndexTypeEnum { @@ -61,6 +69,7 @@ export enum IndexTypeEnum { GLOSSARY = 'glossaryIndex', ABBREVIATIONS = 'abbreviationsIndex', LITERATURE = 'literatureIndex', + OTHER_STANDARDS = 'otherStandardsIndex', } function renderIcon(param: string | undefined): React.ReactNode { @@ -87,6 +96,8 @@ function renderIcon(param: string | undefined): React.ReactNode { return ; case 'Referenz Arbeitshilfen': return ; + case 'Referenz Andere Standards': + return ; case 'Anhang': return ; default: @@ -108,7 +119,7 @@ export interface SectionDetail { replacedContent?: NavMenuItem[]; mergedChildren?: NavMenuItem[]; addedChildren?: NavMenuItem[]; - indexItem?: { key: string; label: string; onClick: () => any }; + indexItem?: { key: string; label: string; children: NavMenuItem[]; onClick?: () => any; onTitleClick?: () => any }; } export type NavMenuItem = { @@ -135,11 +146,10 @@ export function Navigation() { navigationData, setNavigationData, currentSelectedKeys, - // openKeys, + openKeys, } = useDocumentation(); const navigate = useNavigate(); - const location = useLocation(); const [loading, setLoading] = useState(false); @@ -161,6 +171,19 @@ export function Navigation() { //eslint-disable-next-line }, [tailoringParameter.modelVariantId]); + useEffect(() => { + if (navigationData.length > 0) { + const firstMenuItemKey = navigationData[0].key; + if (firstMenuItemKey) { + setSelectedItemKey(firstMenuItemKey); + + navigate(`/documentation/${firstMenuItemKey}${getSearchStringFromHash()}`); + } + } + + //eslint-disable-next-line + }, [navigationData]); + // https://vm-api.weit-verein.de/V-Modellmetamodell/mm_2021/V-Modellvariante/c62d12f444739b2/Kapitel // function getProjectFeaturesString(): string { @@ -183,6 +206,8 @@ export function Navigation() { let currentGeneratedChildren: NavMenuItem[] = []; let i = 0; + const indexesToRemove = []; + for (const child of item.children) { let foundInGeneratedChildren = false; @@ -200,32 +225,55 @@ export function Navigation() { console.log('child.parent null', item); } - const test = await fetchSectionDetailsData(child); - if (test.replacedContent) { - item.children = test.replacedContent; - } else if (test.mergedChildren) { + const sectionDetailsData = await fetchSectionDetailsData(child); + + if (sectionDetailsData == null) { + //item.children.delete [i] = null + const index = item.children.indexOf(child); + // item.children.splice(index, 1); + indexesToRemove.push(index); + } else if (sectionDetailsData.replacedContent) { + item.children = sectionDetailsData.replacedContent; + } else if (sectionDetailsData.mergedChildren) { // ermittle child Einträge! - currentGeneratedChildren = test.mergedChildren; + currentGeneratedChildren = sectionDetailsData.mergedChildren; // TODO: für das erste muss das auch gemacht werden... vllt sollte man es daher rausziehen const foundMenuItem = getMenuItemByAttributeValue(currentGeneratedChildren, 'key', child.key); if (foundMenuItem) { item.children[i] = foundMenuItem; } - } else if (test.addedChildren) { - child.children = test.addedChildren; - } else if (test.indexItem) { - item.children[i] = test.indexItem; + } else if (sectionDetailsData.addedChildren) { + child.children = sectionDetailsData.addedChildren; + } else if (sectionDetailsData.indexItem) { + item.children[i] = sectionDetailsData.indexItem; } } i++; // } } + + // remove empty children + const indexesToRemoveDescending = indexesToRemove.sort(function (a, b) { + return b - a; + }); + for (const removeIndex of indexesToRemoveDescending) { + item.children.splice(removeIndex, 1); + } + + // cleanup menu clicks because inner nodes of the ant tree throw onTitleClick event + // and leaf nodes throw onClick event if (item.children && item.children.length > 0) { - item.onTitleClick = (item: any) => handleSelectedItem(item.key); + // Überschreibt ansonsten das onTitleClick aus dem Index:Arbeitshilfen + if (!item.hasOwnProperty('onTitleClick')) { + item.onTitleClick = (item: any) => handleSelectedItem(item.key); + } await addParentRecursive(item.children); } else { - item.onClick = (item: any) => handleSelectedItem(item.key); + // Überschreibt ansonsten das onClick aus dem Index:Arbeitshilfen + if (!item.hasOwnProperty('onClick')) { + item.onClick = (item: any) => handleSelectedItem(item.key); + } item.children = undefined; } } @@ -234,11 +282,11 @@ export function Navigation() { return items; } - async function fetchSectionDetailsData(childItem: NavMenuItem): Promise { + async function fetchSectionDetailsData(childItem: NavMenuItem): Promise { const sectionId = childItem.key; if (!tailoringParameter.modelVariantId) { - return undefined; + return null; } const sectionDetailUrl = @@ -276,6 +324,11 @@ export function Navigation() { addedChildren = getDisciplineGroup(childItem.parent, jsonDataFromXml).filter((navMenuItem) => disciplineIds.includes(navMenuItem.key) ); + } else if ( + generatedContent === 'Elemente:Produktvorlagen_und_Beispielprodukte_nach_Disziplinen' && + childItem.parent + ) { + addedChildren = await getTemplates(childItem); } else { switch (generatedContent) { case 'Index:Produkte': { @@ -314,7 +367,18 @@ export function Navigation() { indexItem = { key: childItem.key, label: childItem.label, - onClick: () => setSelectedIndexType(IndexTypeEnum.WORK_AIDS), + children: childItem.children, + onClick: + childItem.children?.length > 0 ? () => false : () => setSelectedIndexType(IndexTypeEnum.WORK_AIDS), + onTitleClick: () => setSelectedIndexType(IndexTypeEnum.WORK_AIDS), + }; + break; + } + case 'Index:AndereStandards': { + indexItem = { + key: childItem.key, + label: childItem.label, + onClick: () => setSelectedIndexType(IndexTypeEnum.OTHER_STANDARDS), }; break; } @@ -322,6 +386,10 @@ export function Navigation() { } } + if (childItem.label === 'Glossar') { + addedChildren = await getGlossaryEntries(childItem); + } + if ( childItem.parent?.label === 'Produktabhängigkeiten' && childItem.label === 'Inhaltliche Produktabhängigkeiten' @@ -369,18 +437,19 @@ export function Navigation() { addedChildren = await getDecisionPoints(childItem); } + // do not show Ablaufbausteine in menu if (childItem.parent?.label === 'Referenz Abläufe' && childItem.label === 'Ablaufbausteine') { - addedChildren = await getProcessModules(childItem); + return null; + } + + if (childItem.parent?.label === 'Referenz Andere Standards' && childItem.label === 'Konventionsabbildungen') { + addedChildren = await getConventionFigures(childItem); } if (childItem.parent?.label === 'Produktvorlagen' && childItem.label === 'Übersicht über Produktvorlagen') { addedChildren = await getTemplates(childItem); } - // const sections: Section[] = jsonDataFromXml.getElementsByTagName('Kapitel').map((section: any) => { - // return section.attributes as Section; - // }); - return { // text: textPart, generatedContent: generatedContent, @@ -613,31 +682,55 @@ export function Navigation() { }); } - async function getProcessModules(target: NavMenuItem): Promise { - const processModulesUrl = + async function getConventionFigures(target: NavMenuItem): Promise { + const conventionFiguresUrl = weitApiUrl + - '/Tailoring/V-Modellmetamodell/mm_2021/V-Modellvariante/' + + '/V-Modellmetamodell/mm_2021/V-Modellvariante/' + tailoringParameter.modelVariantId + - '/Projekttyp/' + - tailoringParameter.projectTypeId + - '/Projekttypvariante/' + - tailoringParameter.projectTypeVariantId + - '/Ablaufbaustein?' + - getProjectFeaturesQueryString(); + '/Konventionsabbildung/'; - const jsonDataFromXml = await getJsonDataFromXml(processModulesUrl); + const jsonDataFromXml = await getJsonDataFromXml(conventionFiguresUrl); - const ablaufbausteine: XMLElement[] = jsonDataFromXml.getElementsByTagName('Ablaufbaustein'); + const conventionFigures: XMLElement[] = jsonDataFromXml.getElementsByTagName('Konventionsabbildung'); - return ablaufbausteine.map((processModuleValue) => { - return { - key: processModuleValue.attributes.id, + const conventionFigureEntries = []; + + for (const conventionFigure of conventionFigures) { + const conventionFigureEntry: NavMenuItem = { + key: conventionFigure.attributes.id, parent: target, - label: processModuleValue.attributes.name, - dataType: NavTypeEnum.PROCESS_MODULE, - onClick: (item: any) => handleSelectedItem(item.key), + label: conventionFigure.attributes.name, + dataType: NavTypeEnum.CONVENTION_FIGURE, + onTitleClick: (item: any) => handleSelectedItem(item.key), }; - }); + + //////////////////// + + const divisionsFiguresUrl = + weitApiUrl + + '/V-Modellmetamodell/mm_2021/V-Modellvariante/' + + tailoringParameter.modelVariantId + + '/Konventionsabbildung/' + + conventionFigure.attributes.id; + + const jsonDataFromXml2 = await getJsonDataFromXml(divisionsFiguresUrl); + + const divisions: NavMenuItem[] = jsonDataFromXml2.getElementsByTagName('Bereich').map((division): NavMenuItem => { + return { + key: division.attributes.id, + parent: conventionFigureEntry, + label: decodeXml(division.attributes.name), + dataType: NavTypeEnum.DIVISION, + onClick: (item: any) => handleSelectedItem(item.key), + // children: [], + }; + }); + + conventionFigureEntry.children = divisions; + conventionFigureEntries.push(conventionFigureEntry); + } + + return conventionFigureEntries; } async function getContentProductDependencies(target: NavMenuItem): Promise { @@ -797,6 +890,23 @@ export function Navigation() { }); } + async function getGlossaryEntries(target: NavMenuItem): Promise { + const expressionsUrl = + weitApiUrl + '/V-Modellmetamodell/mm_2021/V-Modellvariante/' + tailoringParameter.modelVariantId + '/Begriff/'; + + const jsonDataFromXml = await getJsonDataFromXml(expressionsUrl); + + return jsonDataFromXml.getElementsByTagName('Begriff').map((expression) => { + return { + key: expression.attributes.id, + parent: target, + label: expression.attributes.name, + dataType: NavTypeEnum.GLOSSARY_ENTRY, + onClick: (item: any) => handleSelectedItem(item.key), + }; + }); + } + async function getTemplates(target: NavMenuItem): Promise { const templatesUrl = weitApiUrl + @@ -916,15 +1026,21 @@ export function Navigation() { const navigation: NavMenuItem[] = []; - jsonDataFromXmlProjectTypeVariants.getElementsByTagName('Projekttypvariante').map((projectTypeVariantValue) => { - navigation.push({ - key: 'pes_' + projectTypeVariantValue.attributes.id, - parent: target, - label: projectTypeVariantValue.attributes.name, - dataType: NavTypeEnum.PROJECT_TYPE_VARIANT_SEQUENCE, // TODO: oder auch ProjectExecutionStrategy - onClick: (item: any) => handleSelectedItem(item.key), // TODO: different Types + jsonDataFromXmlProjectTypeVariants + .getElementsByTagName('Projekttypvariante') + .filter( + (projectTypeVariantValue) => projectTypeVariantValue.attributes.id === tailoringParameter.projectTypeVariantId + ) + .map((projectTypeVariantValue) => { + navigation.push({ + key: 'pes_' + projectTypeVariantValue.attributes.id, + parent: target, + label: projectTypeVariantValue.attributes.name, + dataType: NavTypeEnum.PROJECT_TYPE_VARIANT_SEQUENCE, // TODO: oder auch ProjectExecutionStrategy + onClick: (item: any) => handleSelectedItem(item.key), // TODO: different Types + onTitleClick: (item: any) => handleSelectedItem(item.key), // TODO: different Types + }); }); - }); return navigation; } @@ -957,7 +1073,7 @@ export function Navigation() { inlineIndent={12} items={navigationData} selectedKeys={currentSelectedKeys} - // openKeys={openKeys} // TODO: funzt noch nicht + openKeys={openKeys} /> )} diff --git a/client/src/components/projekthandbuch/projekt/modelVariant.tsx b/client/src/components/projekthandbuch/projekt/modelVariant.tsx index 315a79d..7263856 100644 --- a/client/src/components/projekthandbuch/projekt/modelVariant.tsx +++ b/client/src/components/projekthandbuch/projekt/modelVariant.tsx @@ -27,15 +27,15 @@ export function ModelVariantComponent() { }, []); async function fetchModelVariantsData(): Promise { - const modelVariantsUrl = weitApiUrl + '/V-Modellmetamodell/mm_2021/V-Modellvariante'; + const metaModelUrl = weitApiUrl + '/V-Modellmetamodell/mm_2021'; - const jsonDataFromXml = await getJsonDataFromXml(modelVariantsUrl); + const jsonDataFromXml = await getJsonDataFromXml(metaModelUrl); const modelVariants: ModelVariant[] = jsonDataFromXml .getElementsByTagName('V-Modellvariante') .map((variante) => { const currentAttributes = variante.attributes; - return { id: currentAttributes.id, name: currentAttributes.name }; + return { id: currentAttributes.id, name: currentAttributes.name + ' (v' + currentAttributes.version + ')' }; }) .sort(function (a, b) { return a.name < b.name ? -1 : 1; diff --git a/client/src/components/projekthandbuch/projekt/projectTypeVariant.tsx b/client/src/components/projekthandbuch/projekt/projectTypeVariant.tsx index 87ae14c..2a68b3f 100644 --- a/client/src/components/projekthandbuch/projekt/projectTypeVariant.tsx +++ b/client/src/components/projekthandbuch/projekt/projectTypeVariant.tsx @@ -133,12 +133,14 @@ export function ProjectTypeVariantComponent() { { - setTailoringParameter({ - modelVariantId: tailoringParameter.modelVariantId, - projectTypeVariantId: value[1], - projectTypeId: await getProjectTypeId(value[1]), - }); + onChange={async (value: (string | number)[]) => { + if (tailoringParameter.projectTypeVariantId !== value[1]) { + setTailoringParameter({ + modelVariantId: tailoringParameter.modelVariantId, + projectTypeVariantId: value[1], + projectTypeId: await getProjectTypeId(String(value[1])), + }); + } }} value={cascaderDefaultValue} placeholder={t('common.PleaseChoose')} diff --git a/client/src/context/DocumentationContext.tsx b/client/src/context/DocumentationContext.tsx index 7d32e8f..e1805d2 100644 --- a/client/src/context/DocumentationContext.tsx +++ b/client/src/context/DocumentationContext.tsx @@ -22,8 +22,9 @@ type DocumentationSession = { productId: string | null; contentProductDependencyId: string | null; roleId: string | null; - processModuleId: string | null; decisionPointId: string | null; + conventionFigureId: string | null; + divisionId: string | null; processBuildingBlockId: string | null; methodReferenceId: string | null; toolReferenceId: string | null; @@ -34,6 +35,7 @@ type DocumentationSession = { activityId: string | null; templateDisciplineId: string | null; productDisciplineId: string | null; + glossaryEntryId: string | null; entryId: string | null; getNavigationPath: Function; onRouteChanged: Function; @@ -68,8 +70,9 @@ const DocumentationSessionContextProvider = ({ children }: DocumentationSessionP const [productId, setProductId] = useState(null); const [contentProductDependencyId, setContentProductDependencyId] = useState(null); const [roleId, setRoleId] = useState(null); - const [processModuleId, setProcessModuleId] = useState(null); const [decisionPointId, setDecisionPointId] = useState(null); + const [conventionFigureId, setConventionFigureId] = useState(null); + const [divisionId, setDivisionId] = useState(null); const [processBuildingBlockId, setProcessBuildingBlockId] = useState(null); const [methodReferenceId, setMethodReferenceId] = useState(null); const [toolReferenceId, setToolReferenceId] = useState(null); @@ -80,6 +83,7 @@ const DocumentationSessionContextProvider = ({ children }: DocumentationSessionP const [activityId, setActivityId] = useState(null); const [templateDisciplineId, setTemplateDisciplineId] = useState(null); const [productDisciplineId, setProductDisciplineId] = useState(null); + const [glossaryEntryId, setGlossaryEntryId] = useState(null); const [entryId, setEntryId] = useState(null); const value: DocumentationSession = { @@ -96,8 +100,9 @@ const DocumentationSessionContextProvider = ({ children }: DocumentationSessionP productId, contentProductDependencyId, roleId, - processModuleId, decisionPointId, + conventionFigureId, + divisionId, processBuildingBlockId, methodReferenceId, toolReferenceId, @@ -108,6 +113,7 @@ const DocumentationSessionContextProvider = ({ children }: DocumentationSessionP activityId, templateDisciplineId, productDisciplineId, + glossaryEntryId, entryId, getNavigationPath, onRouteChanged, @@ -126,7 +132,7 @@ const DocumentationSessionContextProvider = ({ children }: DocumentationSessionP // mount().then(); //eslint-disable-next-line - }, [selectedItemKey]); // TODO selectedIndexType + }, [selectedItemKey]); useEffect(() => { // async function mount() { @@ -137,17 +143,16 @@ const DocumentationSessionContextProvider = ({ children }: DocumentationSessionP // mount().then(); //eslint-disable-next-line - }, [selectedIndexType]); // TODO selectedIndexType - - // TODO: onIndexPageChanged + }, [selectedIndexType]); function resetSelectedMenuEntryId() { setDisciplineId(null); setProductId(null); setContentProductDependencyId(null); setRoleId(null); - setProcessModuleId(null); setDecisionPointId(null); + setConventionFigureId(null); + setDivisionId(null); setProcessBuildingBlockId(null); setMethodReferenceId(null); setToolReferenceId(null); @@ -158,10 +163,12 @@ const DocumentationSessionContextProvider = ({ children }: DocumentationSessionP setActivityId(null); setTemplateDisciplineId(null); setProductDisciplineId(null); + setGlossaryEntryId(null); setEntryId(null); } function onRouteChanged(menuEntryId: string): void { + setSelectedIndexType(undefined); resetSelectedMenuEntryId(); const foundMenuItem = getMenuItemByAttributeValue(navigationData, 'key', menuEntryId); @@ -185,10 +192,12 @@ const DocumentationSessionContextProvider = ({ children }: DocumentationSessionP ) ) { setRoleId(foundMenuItem.key); - } else if (foundMenuItem.dataType === NavTypeEnum.PROCESS_MODULE) { - setProcessModuleId(foundMenuItem.key); } else if (foundMenuItem.dataType === NavTypeEnum.DECISION_POINT) { setDecisionPointId(foundMenuItem.key); + } else if (foundMenuItem.dataType === NavTypeEnum.CONVENTION_FIGURE) { + setConventionFigureId(foundMenuItem.key); + } else if (foundMenuItem.dataType === NavTypeEnum.DIVISION) { + setDivisionId(foundMenuItem.key); } else if (foundMenuItem.dataType === NavTypeEnum.PROCESS_BUILDING_BLOCK) { setProcessBuildingBlockId(foundMenuItem.key); } else if (foundMenuItem.dataType === NavTypeEnum.METHOD_REFERENCE) { @@ -207,6 +216,8 @@ const DocumentationSessionContextProvider = ({ children }: DocumentationSessionP setActivityId(foundMenuItem.key); } else if (foundMenuItem.dataType === NavTypeEnum.TEMPLATE_DISCIPLINE) { setTemplateDisciplineId(foundMenuItem.key); + } else if (foundMenuItem.dataType === NavTypeEnum.GLOSSARY_ENTRY) { + setGlossaryEntryId(foundMenuItem.key); } else { setEntryId(foundMenuItem.key); } @@ -235,7 +246,7 @@ const DocumentationSessionContextProvider = ({ children }: DocumentationSessionP } function onIndexPageSelected(indexPageType: IndexTypeEnum): void { - console.log('onIndexPageSelected content', indexPageType); + resetSelectedMenuEntryId(); setSelectedIndexType(indexPageType); } diff --git a/client/src/shares/utils.ts b/client/src/shares/utils.ts index cc3b573..3af1722 100644 --- a/client/src/shares/utils.ts +++ b/client/src/shares/utils.ts @@ -81,6 +81,15 @@ export function getSearchStringFromHash() { return searchHash.substring(searchHash.indexOf('?')); } +export function getFigureDesignationFromText(text: string) { + const matches = text.match(/\[Abb:(.*?)\]/); + + if (matches) { + return matches[1]; + } + return null; +} + export function fixLinksInText(testString: string): string { const url = '#/documentation/'; @@ -107,6 +116,15 @@ export function replaceUrlInText(text: string, tailoringParameter: any, projectF ); } +export function replaceImageUrlInText(text: string, tailoringParameter: any): string { + return text.replace( + /src=['"](?:[^"'\/]*\/)*([^'"]+)['"]/g, + 'src="https://vm-api.weit-verein.de/Tailoring/V-Modellmetamodell/mm_2021/V-Modellvariante/' + + tailoringParameter.modelVariantId + + '/Projekttyp/xxx/Projekttypvariante/xxx/Grafik/images/$1"' + ); +} + export function removeLinksFromHtml(htmlString: string) { const elem = document.createElement('div'); elem.innerHTML = htmlString; diff --git a/pom.xml b/pom.xml index 22c49b2..d4f5bc1 100644 --- a/pom.xml +++ b/pom.xml @@ -158,7 +158,7 @@ com.amashchenko.maven.plugin gitflow-maven-plugin - 1.16.0 + 1.21.0 -s .mvn/settings.xml -DskipTests -P production-ci