From 118b694b15f2547c8237bb99ad72ebe2ea6f2818 Mon Sep 17 00:00:00 2001
From: Sebastian Eicke <sebastian.eicke@HARTING.com>
Date: Mon, 9 Dec 2024 14:02:29 +0100
Subject: [PATCH] Add administrative information component (#111)

* Add administrative information component

* adds missing check for keys

* Adds missing checks (optional chaining)

* Harmonize v-list-items

* Add expansion panel

* Fix margin

---------

Co-authored-by: Aaron Zielstorff <aaron.zi@web.de>
---
 .../AppNavigation/AASListDetails.vue          |  20 +-
 .../src/components/SubmodelElementView.vue    |  18 +-
 .../AdministrativeInformationElement.vue      | 248 ++++++++++++++++++
 3 files changed, 282 insertions(+), 4 deletions(-)
 create mode 100644 aas-web-ui/src/components/UIComponents/AdministrativeInformationElement.vue

diff --git a/aas-web-ui/src/components/AppNavigation/AASListDetails.vue b/aas-web-ui/src/components/AppNavigation/AASListDetails.vue
index ffd0a8e..0ca4f5a 100644
--- a/aas-web-ui/src/components/AppNavigation/AASListDetails.vue
+++ b/aas-web-ui/src/components/AppNavigation/AASListDetails.vue
@@ -40,7 +40,7 @@
                     <AssetInformation
                         v-if="assetInformation && Object.keys(assetInformation).length > 0"
                         :asset-object="assetInformation"></AssetInformation>
-                    <v-divider v-if="assetInformation"></v-divider>
+                    <v-divider v-if="assetInformation" thickness="2"></v-divider>
                     <!-- AAS Details -->
                     <v-list v-if="detailsObject" lines="one" nav class="bg-detailsCard">
                         <!-- AAS Identification -->
@@ -50,10 +50,22 @@
                             :model-type="'AAS'"
                             :id-type="'Identification (ID)'"
                             :name-type="'idShort'"></IdentificationElement>
+                        <!-- AAS Administrative Information-->
                         <v-divider
-                            v-if="detailsObject.displayName && detailsObject.displayName.length > 0"
+                            v-if="
+                                detailsObject.administration &&
+                                (detailsObject.administration.revision != '' ||
+                                    detailsObject.administration.version != '')
+                            "
                             class="mt-2"></v-divider>
-                        <!-- SubmodelELement DisplayName -->
+                        <AdministrativeInformationElement
+                            v-if="detailsObject.administration"
+                            :administrative-information-object="detailsObject.administration"
+                            :administrative-information-title="'Administrative Information'"
+                            :small="false"
+                            :background-color="'detailsCard'"></AdministrativeInformationElement>
+                        <v-divider v-if="detailsObject.displayName && detailsObject.displayName.length > 0"></v-divider>
+                        <!-- AAS DisplayName -->
                         <DisplayNameElement
                             v-if="detailsObject.displayName && detailsObject.displayName.length > 0"
                             :display-name-object="detailsObject.displayName"
@@ -77,6 +89,7 @@
 
 <script lang="ts">
     import { defineComponent } from 'vue';
+    import AdministrativeInformationElement from '@/components/UIComponents/AdministrativeInformationElement.vue';
     import AssetInformation from '@/components/UIComponents/AssetInformation.vue';
     import DescriptionElement from '@/components/UIComponents/DescriptionElement.vue';
     import DisplayNameElement from '@/components/UIComponents/DisplayNameElement.vue';
@@ -89,6 +102,7 @@
         name: 'AASListDetails',
         components: {
             IdentificationElement,
+            AdministrativeInformationElement,
             DisplayNameElement,
             DescriptionElement,
             AssetInformation,
diff --git a/aas-web-ui/src/components/SubmodelElementView.vue b/aas-web-ui/src/components/SubmodelElementView.vue
index 028fc93..f497ccf 100644
--- a/aas-web-ui/src/components/SubmodelElementView.vue
+++ b/aas-web-ui/src/components/SubmodelElementView.vue
@@ -13,9 +13,23 @@
                             :model-type="submodelElementData.modelType"
                             :id-type="'Identification (ID)'"
                             :name-type="'idShort'"></IdentificationElement>
+                        <!-- Submodel Administrative Information-->
                         <v-divider
-                            v-if="submodelElementData.displayName && submodelElementData.displayName.length > 0"
+                            v-if="
+                                submodelElementData.administration &&
+                                (submodelElementData.administration.revision != '' ||
+                                    submodelElementData.administration.version != '')
+                            "
                             class="mt-2"></v-divider>
+                        <AdministrativeInformationElement
+                            v-if="submodelElementData.administration"
+                            :administrative-information-object="submodelElementData.administration"
+                            :administrative-information-title="'Administrative Information'"
+                            :small="false"></AdministrativeInformationElement>
+                        <v-divider
+                            v-if="
+                                submodelElementData.displayName && submodelElementData.displayName.length > 0
+                            "></v-divider>
                         <!-- SubmodelELement DisplayName -->
                         <DisplayNameElement
                             v-if="submodelElementData.displayName && submodelElementData.displayName.length > 0"
@@ -154,6 +168,7 @@
     import Submodel from '@/components/SubmodelElements/Submodel.vue';
     import SubmodelElementCollection from '@/components/SubmodelElements/SubmodelElementCollection.vue';
     import SubmodelElementList from '@/components/SubmodelElements/SubmodelElementList.vue';
+    import AdministrativeInformationElement from '@/components/UIComponents/AdministrativeInformationElement.vue';
     import ConceptDescription from '@/components/UIComponents/ConceptDescription.vue';
     import DescriptionElement from '@/components/UIComponents/DescriptionElement.vue';
     import DisplayNameElement from '@/components/UIComponents/DisplayNameElement.vue';
@@ -169,6 +184,7 @@
         name: 'SubmodelElementView',
         components: {
             IdentificationElement,
+            AdministrativeInformationElement,
             DisplayNameElement,
             DescriptionElement,
             SemanticID,
diff --git a/aas-web-ui/src/components/UIComponents/AdministrativeInformationElement.vue b/aas-web-ui/src/components/UIComponents/AdministrativeInformationElement.vue
new file mode 100644
index 0000000..1eead98
--- /dev/null
+++ b/aas-web-ui/src/components/UIComponents/AdministrativeInformationElement.vue
@@ -0,0 +1,248 @@
+<template>
+    <v-container fluid class="pa-0">
+        <v-expansion-panels>
+            <v-expansion-panel elevation="0" tile static :color="backgroundColor">
+                <v-expansion-panel-title class="px-2">
+                    <span :class="small ? 'text-caption' : 'text-subtitle-2 '">
+                        {{ administrativeInformationTitle }}
+                    </span>
+                </v-expansion-panel-title>
+                <v-expansion-panel-text :class="'bg-' + backgroundColor">
+                    <v-divider
+                        v-if="
+                            Array.isArray(administrativeInformationObject?.creator?.keys) &&
+                            administrativeInformationObject?.creator?.keys.length > 0
+                        "
+                        class="mb-1"
+                        opacity="0.05"></v-divider>
+                    <v-list nav class="pa-0">
+                        <!-- Creator -->
+                        <v-list-item
+                            v-if="
+                                Array.isArray(administrativeInformationObject?.creator?.keys) &&
+                                administrativeInformationObject?.creator?.keys.length > 0
+                            "
+                            class="ma-0">
+                            <v-tooltip activator="parent" open-delay="600" transition="slide-x-transition">
+                                <div
+                                    v-for="(creator, i) in administrativeInformationObject.creator.keys"
+                                    :key="i"
+                                    class="text-caption">
+                                    <span v-if="creator?.type" class="font-weight-bold">{{
+                                        '(' + creator.type + ') '
+                                    }}</span
+                                    >{{ creator.value }}
+                                </div>
+                            </v-tooltip>
+                            <template #title>
+                                <span class="text-subtitle-2">
+                                    {{
+                                        administrativeInformationObject.creator.keys.length === 1
+                                            ? 'Creator:'
+                                            : 'Creators:'
+                                    }}
+                                </span>
+                            </template>
+                            <v-list-item-subtitle
+                                v-for="(creator, i) in administrativeInformationObject.creator.keys"
+                                :key="i">
+                                <div v-if="creator?.type" class="pt-2">
+                                    <v-chip label size="x-small" border class="mr-2">{{ creator.type }}</v-chip>
+                                    <span>{{ creator.value }}</span>
+                                </div>
+                            </v-list-item-subtitle>
+                        </v-list-item>
+                        <v-divider
+                            v-if="
+                                Array.isArray(administrativeInformationObject?.creator?.keys) &&
+                                administrativeInformationObject?.creator?.keys.length > 0 &&
+                                (administrativeInformationObject?.version || administrativeInformationObject?.revision)
+                            "
+                            class="mt-2"
+                            opacity="0.05"></v-divider>
+                        <!-- Version and Revision -->
+                        <v-list-item
+                            v-if="administrativeInformationObject?.version || administrativeInformationObject?.revision"
+                            class="ma-0">
+                            <v-list-item-title>
+                                <template v-if="administrativeInformationObject?.version">
+                                    <span class="text-subtitle-2 mt-2 mr-2">{{ 'Version:' }}</span
+                                    ><v-chip label size="x-small" border class="mr-5">{{
+                                        administrativeInformationObject.version
+                                    }}</v-chip>
+                                </template>
+                                <template v-if="administrativeInformationObject?.revision">
+                                    <span class="text-subtitle-2 mt-2 mr-2">{{ 'Revision:' }}</span
+                                    ><v-chip label size="x-small" border class="mr-5">{{
+                                        administrativeInformationObject.revision
+                                    }}</v-chip>
+                                </template>
+                            </v-list-item-title>
+                        </v-list-item>
+                    </v-list>
+                    <v-divider
+                        v-if="
+                            ((Array.isArray(administrativeInformationObject?.creator?.keys) &&
+                                administrativeInformationObject?.creator?.keys.length > 0) ||
+                                administrativeInformationObject?.version ||
+                                administrativeInformationObject?.revision) &&
+                            administrativeInformationObject?.templateId
+                        "
+                        opacity="0.05"></v-divider>
+                    <v-list nav class="pa-0">
+                        <v-hover v-slot="{ isHovering, props }">
+                            <v-list-item v-if="administrativeInformationObject?.templateId" class="ma-0">
+                                <template #title>
+                                    <span class="text-subtitle-2">
+                                        {{ 'Template ID:' }}
+                                    </span>
+                                </template>
+                                <template #subtitle>
+                                    <div
+                                        v-if="administrativeInformationObject.templateId"
+                                        v-bind="props"
+                                        :class="isHovering ? 'cursor-pointer' : ''"
+                                        @click="
+                                            copyToClipboard(administrativeInformationObject.templateId, 'Template ID')
+                                        ">
+                                        <v-icon v-if="isHovering" color="subtitleText" size="x-small" class="mr-1">{{
+                                            copyIcon
+                                        }}</v-icon>
+                                        <span>{{ administrativeInformationObject.templateId }}</span>
+                                    </div>
+                                </template>
+                            </v-list-item>
+                        </v-hover>
+                    </v-list>
+                    <v-divider
+                        v-if="
+                            ((Array.isArray(administrativeInformationObject?.creator?.keys) &&
+                                administrativeInformationObject?.creator?.keys.length > 0) ||
+                                administrativeInformationObject?.version ||
+                                administrativeInformationObject?.revision ||
+                                administrativeInformationObject?.templateId) &&
+                            Array.isArray(administrativeInformationObject?.embeddedDataSpecifications) &&
+                            administrativeInformationObject?.embeddedDataSpecifications.length > 0
+                        "
+                        opacity="0.05"></v-divider>
+                    <!-- Embedded Data Specifications -->
+                    <v-list
+                        v-if="
+                            Array.isArray(administrativeInformationObject?.embeddedDataSpecifications) &&
+                            administrativeInformationObject?.embeddedDataSpecifications.length > 0
+                        "
+                        nav
+                        class="pa-0">
+                        <v-card
+                            v-for="(
+                                embeddedDataSpecification, i
+                            ) in administrativeInformationObject.embeddedDataSpecifications"
+                            :key="i"
+                            color="elevatedCard"
+                            class="mt-2">
+                            <v-list nav class="bg-elevatedCard pt-0">
+                                <!-- hasDataSpecification -->
+                                <SemanticID
+                                    v-if="
+                                        Array.isArray(embeddedDataSpecification?.dataSpecification?.keys) &&
+                                        embeddedDataSpecification?.dataSpecification?.keys.length > 0
+                                    "
+                                    :semantic-id-object="embeddedDataSpecification.dataSpecification"
+                                    :semantic-title="'Data Specification'"
+                                    class="mb-2"></SemanticID>
+                                <v-divider v-if="embeddedDataSpecification?.dataSpecificationContent"></v-divider>
+                                <!-- dataSpecificationContent -->
+                                <DataSpecificationContent
+                                    v-if="embeddedDataSpecification?.dataSpecificationContent"
+                                    :data-specification-object="
+                                        embeddedDataSpecification?.dataSpecificationContent
+                                    "></DataSpecificationContent>
+                            </v-list>
+                        </v-card>
+                    </v-list>
+                </v-expansion-panel-text>
+            </v-expansion-panel>
+        </v-expansion-panels>
+    </v-container>
+</template>
+
+<script lang="ts">
+    import { defineComponent } from 'vue';
+    import { useNavigationStore } from '@/store/NavigationStore';
+    import DataSpecificationContent from './DataSpecificationContent.vue';
+    import SemanticID from './SemanticID.vue';
+
+    export default defineComponent({
+        name: 'AdministrativeInformationElement',
+        components: {
+            SemanticID,
+            DataSpecificationContent,
+        },
+        // props: ['administrativeInformationObject', 'administrativeInformationTitle', 'small', 'backgroundColor'],
+        props: {
+            administrativeInformationObject: {
+                type: Object,
+                default: () => ({}),
+            },
+            administrativeInformationTitle: {
+                type: String,
+                default: '',
+            },
+            small: {
+                type: Boolean,
+                default: true,
+            },
+            backgroundColor: {
+                type: String,
+                default: '',
+            },
+        },
+
+        setup() {
+            const navigationStore = useNavigationStore();
+
+            return {
+                navigationStore, // NavigationStore Object
+            };
+        },
+
+        data() {
+            return {
+                copyIcon: 'mdi-clipboard-file-outline',
+            };
+        },
+
+        methods: {
+            // Function to copy the id to the clipboard
+            copyToClipboard(value: string, valueName: string) {
+                if (!value || !value) return;
+                // console.log('Copy ID to Clipboard: ', this.identificationObject.id);
+                // set the icon to checkmark
+                this.copyIcon = 'mdi-check';
+                // copy the path to the clipboard
+                navigator.clipboard.writeText(value);
+                // set the clipboard tooltip to false after 1.5 seconds
+                setTimeout(() => {
+                    this.copyIcon = 'mdi-clipboard-file-outline';
+                }, 2000);
+                // open Snackbar to inform the user that the path was copied to the clipboard
+                this.navigationStore.dispatchSnackbar({
+                    status: true,
+                    timeout: 2000,
+                    color: 'success',
+                    btnColor: 'buttonText',
+                    text: valueName + ' copied to Clipboard.',
+                });
+            },
+        },
+    });
+</script>
+
+<style lang="css" scoped>
+    .v-expansion-panel-text :deep(.v-expansion-panel-text__wrapper) {
+        padding-left: 8px !important;
+        padding-right: 8px !important;
+        padding-top: 0px !important;
+        padding-bottom: 12px !important;
+    }
+</style>