From 716e0f24d7729954d6b062690cfa61654579678d Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 13 Aug 2025 13:00:42 +0200 Subject: [PATCH 1/3] Workin on the organisation dashboard --- compare_archimate.php | 272 +++++++++++------- lib/Service/ArchiMateExportService.php | 65 +++-- lib/Settings/softwarecatalogus_register.json | 29 +- src/components/GenericObjectTable.vue | 182 ++++++++++-- src/main.js | 3 + src/navigation/MainMenu.vue | 29 +- src/store/modules/object.js | 112 +++++++- src/store/modules/settings.js | 106 +++---- src/views/Views.vue | 9 + .../contactpersonen/ContactpersoonIndex.vue | 243 ++++++++++++++++ src/views/contracten/ContractIndex.vue | 250 ++++++++++++++++ src/views/organisaties/OrganisatieIndex.vue | 50 ++-- src/views/voorzieningen/VoorzieningIndex.vue | 243 ++++++++++++++++ 13 files changed, 1346 insertions(+), 247 deletions(-) create mode 100644 src/views/contactpersonen/ContactpersoonIndex.vue create mode 100644 src/views/contracten/ContractIndex.vue create mode 100644 src/views/voorzieningen/VoorzieningIndex.vue diff --git a/compare_archimate.php b/compare_archimate.php index 0dea58e2..15237fe5 100644 --- a/compare_archimate.php +++ b/compare_archimate.php @@ -173,68 +173,70 @@ private function compareElements(\SimpleXMLElement $original, \SimpleXMLElement { echo "\n--- Comparing Elements ---\n"; - $originalElements = $original->xpath('//*[local-name()="element"]'); - $exportedElements = $exported->xpath('//*[local-name()="element"]'); + $originalElements = $this->indexByAttribute($original->xpath('//*[local-name()="elements"]/*[local-name()="element"]'), 'identifier'); + $exportedElements = $this->indexByAttribute($exported->xpath('//*[local-name()="elements"]/*[local-name()="element"]'), 'identifier'); $this->stats['elements_compared'] = count($originalElements); - echo sprintf("Original elements found: %d\n", count($originalElements)); - echo sprintf("Exported elements found: %d\n", count($exportedElements)); + echo "Original elements found: " . count($originalElements) . "\n"; + echo "Exported elements found: " . count($exportedElements) . "\n"; - // Check if exported elements have identifiers - $exportedHasIdentifiers = false; - $exportedHasXsiType = false; + // Check for extra elements in export + $extraElements = array_diff_key($exportedElements, $originalElements); + if (!empty($extraElements)) { + echo "⚠️ WARNING: Found " . count($extraElements) . " extra elements in export!\n"; + foreach (array_slice($extraElements, 0, 5) as $id => $element) { + $this->addDifference('elements', $id, 'MISSING', 'EXTRA_IN_EXPORT'); + } + if (count($extraElements) > 5) { + echo "... and " . (count($extraElements) - 5) . " more extra elements\n"; + } + } - if (!empty($exportedElements)) { - $firstExported = $exportedElements[0]; - $exportedHasIdentifiers = !empty((string)$firstExported['identifier']); - $exportedHasXsiType = !empty((string)$firstExported->attributes('xsi', true)['type']); - - echo sprintf("Exported elements have identifier attribute: %s\n", - $exportedHasIdentifiers ? 'YES' : 'NO'); - echo sprintf("Exported elements have xsi:type attribute: %s\n", - $exportedHasXsiType ? 'YES' : 'NO'); + // Check for missing elements in export + $missingElements = array_diff_key($originalElements, $exportedElements); + if (!empty($missingElements)) { + echo "⚠️ WARNING: Found " . count($missingElements) . " missing elements in export!\n"; + foreach (array_slice($missingElements, 0, 5) as $id => $element) { + $this->addDifference('elements', $id, 'EXISTS', 'MISSING_IN_EXPORT'); + } + if (count($missingElements) > 5) { + echo "... and " . (count($missingElements) - 5) . " more missing elements\n"; + } } - // If exported elements don't have identifiers, we can't do detailed comparison - if (!$exportedHasIdentifiers && !empty($originalElements)) { - echo "❌ CRITICAL: Exported elements missing identifier attributes!\n"; - $this->addDifference('elements', 'ALL', 'HAS_IDENTIFIERS', 'MISSING_IDENTIFIERS'); - - // Show sample of what's missing - $sampleOriginal = $originalElements[0]; - echo sprintf("Expected: \n", - (string)$sampleOriginal['identifier'], - (string)$sampleOriginal->attributes('xsi', true)['type'] - ); - echo sprintf("Actual: (no attributes)\n"); - return; + // Check if exported elements have required attributes + $hasIdentifier = true; + $hasXsiType = true; + foreach (array_slice($exportedElements, 0, 10) as $element) { + if (!isset($element->attributes()['identifier'])) { + $hasIdentifier = false; + } + // Check for xsi:type in the xsi namespace + $xsiAttributes = $element->attributes('xsi', true); + if (!isset($xsiAttributes['type'])) { + $hasXsiType = false; + } } - // Do detailed comparison if identifiers exist - $originalIndexed = $this->indexByAttribute($originalElements, 'identifier'); - $exportedIndexed = $this->indexByAttribute($exportedElements, 'identifier'); + echo "Exported elements have identifier attribute: " . ($hasIdentifier ? 'YES' : 'NO') . "\n"; + echo "Exported elements have xsi:type attribute: " . ($hasXsiType ? 'YES' : 'NO') . "\n"; - foreach ($originalIndexed as $id => $originalElement) { - if (!isset($exportedIndexed[$id])) { - $this->addDifference('elements', $id, 'EXISTS', 'MISSING'); - continue; + // Compare common elements + $commonElements = array_intersect_key($originalElements, $exportedElements); + $comparedCount = 0; + foreach ($commonElements as $id => $originalElement) { + if ($comparedCount >= 100) { // Limit comparison to first 100 elements for performance + break; } - $exportedElement = $exportedIndexed[$id]; - - // Compare xsi:type - $originalType = (string)$originalElement->attributes('xsi', true)['type']; - $exportedType = (string)$exportedElement->attributes('xsi', true)['type']; - if ($originalType !== $exportedType) { - $this->addDifference('elements', $id . '/xsi:type', $originalType, $exportedType); - } + $exportedElement = $exportedElements[$id]; // Compare name - if ((string)$originalElement->name !== (string)$exportedElement->name) { - $this->addDifference('elements', $id . '/name', - (string)$originalElement->name, - (string)$exportedElement->name); + $originalName = (string)$originalElement->name; + $exportedName = (string)$exportedElement->name; + if ($originalName !== $exportedName) { + $this->addDifference('elements', $id . '/name', $originalName, $exportedName); } // Compare documentation @@ -246,56 +248,64 @@ private function compareElements(\SimpleXMLElement $original, \SimpleXMLElement // Compare properties $this->compareProperties($originalElement, $exportedElement, 'elements', $id); + + $comparedCount++; } - // Check for extra elements in export - foreach ($exportedElements as $id => $exportedElement) { - if (!isset($originalElements[$id])) { - $this->addDifference('elements', $id, 'MISSING', 'EXISTS'); - } + if (count($commonElements) > 100) { + echo "Note: Only compared first 100 elements for performance\n"; } - echo "Elements comparison completed: " . count($originalElements) . " elements\n"; + echo "Elements comparison completed: " . $comparedCount . " elements\n"; } private function compareRelationships(\SimpleXMLElement $original, \SimpleXMLElement $exported): void { echo "\n--- Comparing Relationships ---\n"; - $originalRels = $this->indexByAttribute($original->xpath('//*[local-name()="relationship"]'), 'identifier'); - $exportedRels = $this->indexByAttribute($exported->xpath('//*[local-name()="relationship"]'), 'identifier'); + $originalRels = $this->indexByAttribute($original->xpath('//*[local-name()="relationships"]/*[local-name()="relationship"]'), 'identifier'); + $exportedRels = $this->indexByAttribute($exported->xpath('//*[local-name()="relationships"]/*[local-name()="relationship"]'), 'identifier'); $this->stats['relationships_compared'] = count($originalRels); - foreach ($originalRels as $id => $originalRel) { - if (!isset($exportedRels[$id])) { - $this->addDifference('relationships', $id, 'EXISTS', 'MISSING'); - continue; + echo "Original relationships found: " . count($originalRels) . "\n"; + echo "Exported relationships found: " . count($exportedRels) . "\n"; + + // Check for extra relationships in export + $extraRels = array_diff_key($exportedRels, $originalRels); + if (!empty($extraRels)) { + echo "⚠️ WARNING: Found " . count($extraRels) . " extra relationships in export!\n"; + foreach (array_slice($extraRels, 0, 5) as $id => $rel) { + $this->addDifference('relationships', $id, 'MISSING', 'EXTRA_IN_EXPORT'); } - - $exportedRel = $exportedRels[$id]; - - // Compare xsi:type - $originalType = (string)$originalRel->attributes('xsi', true)['type']; - $exportedType = (string)$exportedRel->attributes('xsi', true)['type']; - if ($originalType !== $exportedType) { - $this->addDifference('relationships', $id . '/xsi:type', $originalType, $exportedType); + if (count($extraRels) > 5) { + echo "... and " . (count($extraRels) - 5) . " more extra relationships\n"; } - - // Compare source and target - $originalSource = (string)$originalRel->attributes()['source']; - $exportedSource = (string)$exportedRel->attributes()['source']; - if ($originalSource !== $exportedSource) { - $this->addDifference('relationships', $id . '/source', $originalSource, $exportedSource); + } + + // Check for missing relationships in export + $missingRels = array_diff_key($originalRels, $exportedRels); + if (!empty($missingRels)) { + echo "⚠️ WARNING: Found " . count($missingRels) . " missing relationships in export!\n"; + foreach (array_slice($missingRels, 0, 5) as $id => $rel) { + $this->addDifference('relationships', $id, 'EXISTS', 'MISSING_IN_EXPORT'); } - - $originalTarget = (string)$originalRel->attributes()['target']; - $exportedTarget = (string)$exportedRel->attributes()['target']; - if ($originalTarget !== $exportedTarget) { - $this->addDifference('relationships', $id . '/target', $originalTarget, $exportedTarget); + if (count($missingRels) > 5) { + echo "... and " . (count($missingRels) - 5) . " more missing relationships\n"; + } + } + + // Compare common relationships (limit to first 100 for performance) + $commonRels = array_intersect_key($originalRels, $exportedRels); + $comparedCount = 0; + foreach ($commonRels as $id => $originalRel) { + if ($comparedCount >= 100) { + break; } - // Compare name (if present) + $exportedRel = $exportedRels[$id]; + + // Compare name $originalName = (string)$originalRel->name; $exportedName = (string)$exportedRel->name; if ($originalName !== $exportedName) { @@ -311,9 +321,15 @@ private function compareRelationships(\SimpleXMLElement $original, \SimpleXMLEle // Compare properties $this->compareProperties($originalRel, $exportedRel, 'relationships', $id); + + $comparedCount++; + } + + if (count($commonRels) > 100) { + echo "Note: Only compared first 100 relationships for performance\n"; } - echo "Relationships comparison completed: " . count($originalRels) . " relationships\n"; + echo "Relationships comparison completed: " . $comparedCount . " relationships\n"; } private function compareOrganizations(\SimpleXMLElement $original, \SimpleXMLElement $exported): void @@ -351,6 +367,33 @@ private function comparePropertyDefinitions(\SimpleXMLElement $original, \Simple $this->stats['property_definitions_compared'] = count($originalProps); + echo "Original property definitions found: " . count($originalProps) . "\n"; + echo "Exported property definitions found: " . count($exportedProps) . "\n"; + + // Check for extra property definitions in export + $extraProps = array_diff_key($exportedProps, $originalProps); + if (!empty($extraProps)) { + echo "⚠️ WARNING: Found " . count($extraProps) . " extra property definitions in export!\n"; + foreach (array_slice($extraProps, 0, 5) as $id => $prop) { + $this->addDifference('property_definitions', $id, 'MISSING', 'EXTRA_IN_EXPORT'); + } + if (count($extraProps) > 5) { + echo "... and " . (count($extraProps) - 5) . " more extra property definitions\n"; + } + } + + // Check for missing property definitions in export + $missingProps = array_diff_key($originalProps, $exportedProps); + if (!empty($missingProps)) { + echo "⚠️ WARNING: Found " . count($missingProps) . " missing property definitions in export!\n"; + foreach (array_slice($missingProps, 0, 5) as $id => $prop) { + $this->addDifference('property_definitions', $id, 'EXISTS', 'MISSING_IN_EXPORT'); + } + if (count($missingProps) > 5) { + echo "... and " . (count($missingProps) - 5) . " more missing property definitions\n"; + } + } + foreach ($originalProps as $id => $originalProp) { if (!isset($exportedProps[$id])) { $this->addDifference('property_definitions', $id, 'EXISTS', 'MISSING'); @@ -409,35 +452,66 @@ private function compareViews(\SimpleXMLElement $original, \SimpleXMLElement $ex { echo "\n--- Comparing Views ---\n"; - $originalViews = $this->indexByAttribute($original->xpath('//*[local-name()="view"]'), 'identifier'); - $exportedViews = $this->indexByAttribute($exported->xpath('//*[local-name()="view"]'), 'identifier'); + $originalViews = $this->indexByAttribute($original->xpath('//*[local-name()="views"]//*[local-name()="view"]'), 'identifier'); + $exportedViews = $this->indexByAttribute($exported->xpath('//*[local-name()="views"]//*[local-name()="view"]'), 'identifier'); $this->stats['views_compared'] = count($originalViews); - foreach ($originalViews as $id => $originalView) { - if (!isset($exportedViews[$id])) { - $this->addDifference('views', $id, 'EXISTS', 'MISSING'); - continue; + echo "Original views found: " . count($originalViews) . "\n"; + echo "Exported views found: " . count($exportedViews) . "\n"; + + // Check for extra views in export + $extraViews = array_diff_key($exportedViews, $originalViews); + if (!empty($extraViews)) { + echo "⚠️ WARNING: Found " . count($extraViews) . " extra views in export!\n"; + foreach (array_slice($extraViews, 0, 5) as $id => $view) { + $this->addDifference('views', $id, 'MISSING', 'EXTRA_IN_EXPORT'); + } + if (count($extraViews) > 5) { + echo "... and " . (count($extraViews) - 5) . " more extra views\n"; + } + } + + // Check for missing views in export + $missingViews = array_diff_key($originalViews, $exportedViews); + if (!empty($missingViews)) { + echo "⚠️ WARNING: Found " . count($missingViews) . " missing views in export!\n"; + foreach (array_slice($missingViews, 0, 5) as $id => $view) { + $this->addDifference('views', $id, 'EXISTS', 'MISSING_IN_EXPORT'); + } + if (count($missingViews) > 5) { + echo "... and " . (count($missingViews) - 5) . " more missing views\n"; + } + } + + // Compare common views (limit to first 50 for performance) + $commonViews = array_intersect_key($originalViews, $exportedViews); + $comparedCount = 0; + foreach ($commonViews as $id => $originalView) { + if ($comparedCount >= 50) { + break; } $exportedView = $exportedViews[$id]; - // Compare xsi:type - $originalType = (string)$originalView->attributes('xsi', true)['type']; - $exportedType = (string)$exportedView->attributes('xsi', true)['type']; - if ($originalType !== $exportedType) { - $this->addDifference('views', $id . '/xsi:type', $originalType, $exportedType); - } - // Compare name - if ((string)$originalView->name !== (string)$exportedView->name) { - $this->addDifference('views', $id . '/name', - (string)$originalView->name, - (string)$exportedView->name); + $originalName = (string)$originalView->name; + $exportedName = (string)$exportedView->name; + if ($originalName !== $exportedName) { + $this->addDifference('views', $id . '/name', $originalName, $exportedName); } + + // Compare properties + $this->compareProperties($originalView, $exportedView, 'views', $id); + + $comparedCount++; + } + + if (count($commonViews) > 50) { + echo "Note: Only compared first 50 views for performance\n"; } - echo "Views comparison completed: " . count($originalViews) . " views\n"; + echo "Views comparison completed: " . $comparedCount . " views\n"; } private function compareProperties(\SimpleXMLElement $originalElement, \SimpleXMLElement $exportedElement, string $section, string $id): void diff --git a/lib/Service/ArchiMateExportService.php b/lib/Service/ArchiMateExportService.php index dd4fe48b..461a1dc1 100644 --- a/lib/Service/ArchiMateExportService.php +++ b/lib/Service/ArchiMateExportService.php @@ -677,7 +677,8 @@ private function addObjectDirectlyToXml(\SimpleXMLElement $folder, array $object if ($sectionName === 'views') { $this->addViewDataToXmlNode($objectNode, $xmlData); } else { - $this->addCleanDataToXmlNode($objectNode, $xmlData); + // Pass section information to help with attribute handling + $this->addCleanDataToXmlNode($objectNode, $xmlData, $sectionName); } } } @@ -890,7 +891,7 @@ private function cleanObjectDataForXml(array $object): array /** * Add clean data to XML node with proper ArchiMate structure */ - private function addCleanDataToXmlNode(\SimpleXMLElement $node, array $data): void + private function addCleanDataToXmlNode(\SimpleXMLElement $node, array $data, ?string $sectionName = null): void { // Extract attributes from various possible locations $attributes = []; @@ -905,8 +906,18 @@ private function addCleanDataToXmlNode(\SimpleXMLElement $node, array $data): vo foreach ($data['_attributes'] as $attrKey => $attrValue) { // Clean up attribute keys (remove colons, etc.) $cleanKey = str_replace(':', '', $attrKey); - if ($cleanKey === 'type' && !isset($attributes['xsi:type'])) { - $attributes['xsi:type'] = (string)$attrValue; + + // Check if this is a property definition to handle 'type' attribute correctly + $isPropertyDefinition = ($sectionName === 'property_definitions'); + + if ($cleanKey === 'type') { + if ($isPropertyDefinition) { + // For property definitions, 'type' should remain as 'type' attribute + $attributes['type'] = (string)$attrValue; + } else { + // For other elements, 'type' becomes 'xsi:type' + $attributes['xsi:type'] = (string)$attrValue; + } } elseif (in_array($cleanKey, ['identifier', 'source', 'target', 'accessType'])) { $attributes[$cleanKey] = (string)$attrValue; } @@ -916,18 +927,17 @@ private function addCleanDataToXmlNode(\SimpleXMLElement $node, array $data): vo // 3. Look for xsi:type in various forms (including double underscore from import service) foreach (['xsi:type', 'xsi_type', '_xsi:type', '_xsi__type', '_type'] as $typeKey) { if (isset($data[$typeKey])) { - // For property definitions, _type should be 'type' attribute, not 'xsi:type' - // Detect property definitions by checking if we have a section field or if this is in property_definitions - $isPropertyDefinition = (isset($data['section']) && $data['section'] === 'property_definitions') || - (isset($data['_section']) && $data['_section'] === 'property_definitions') || - ($typeKey === '_type' && !isset($attributes['xsi:type'])); + // Check if this is a property definition to handle type attributes correctly + $isPropertyDefinition = ($sectionName === 'property_definitions'); - if ($isPropertyDefinition && $typeKey === '_type' && !isset($attributes['type'])) { + if ($typeKey === '_type' && $isPropertyDefinition && !isset($attributes['type'])) { + // For property definitions, _type becomes 'type' attribute $attributes['type'] = (string)$data[$typeKey]; break; - } elseif (!$isPropertyDefinition && !isset($attributes['xsi:type'])) { - $attributes['xsi:type'] = (string)$data[$typeKey]; - break; + } elseif (in_array($typeKey, ['xsi:type', 'xsi_type', '_xsi:type', '_xsi__type']) && !isset($attributes['xsi:type'])) { + // For other elements, these become 'xsi:type' attribute + $attributes['xsi:type'] = (string)$data[$typeKey]; + break; } } } @@ -935,11 +945,20 @@ private function addCleanDataToXmlNode(\SimpleXMLElement $node, array $data): vo // 4. Look for other attributes in various forms foreach (['source', 'target', 'accessType', 'type'] as $attrName) { if (isset($data[$attrName]) && !isset($attributes[$attrName])) { - // For 'type', only use if we don't have xsi:type - if ($attrName === 'type' && isset($attributes['xsi:type'])) { - continue; + // Check if this is a property definition to handle 'type' attribute correctly + $isPropertyDefinition = ($sectionName === 'property_definitions'); + + if ($attrName === 'type') { + if ($isPropertyDefinition) { + // For property definitions, 'type' should remain as 'type' attribute + $attributes['type'] = (string)$data[$attrName]; + } elseif (!isset($attributes['xsi:type'])) { + // For other elements, 'type' becomes 'xsi:type' if not already set + $attributes['xsi:type'] = (string)$data[$attrName]; + } + } else { + $attributes[$attrName] = (string)$data[$attrName]; } - $attributes[$attrName] = (string)$data[$attrName]; } } @@ -967,7 +986,7 @@ private function addCleanDataToXmlNode(\SimpleXMLElement $node, array $data): vo // Handle xml:lang in various forms (including double underscore from import service) foreach (['xml:lang', '_xml:lang', '_xml__lang', 'xml_lang'] as $langKey) { if (isset($value[$langKey])) { - $nameNode->addAttribute('xml:lang', $value[$langKey]); + $nameNode->addAttribute('xml:lang', $value[$langKey], 'http://www.w3.org/XML/1998/namespace'); break; } } @@ -979,7 +998,7 @@ private function addCleanDataToXmlNode(\SimpleXMLElement $node, array $data): vo // Handle xml:lang in various forms (including double underscore from import service) foreach (['xml:lang', '_xml:lang', '_xml__lang', 'xml_lang'] as $langKey) { if (isset($value[$langKey])) { - $docNode->addAttribute('xml:lang', $value[$langKey]); + $docNode->addAttribute('xml:lang', $value[$langKey], 'http://www.w3.org/XML/1998/namespace'); break; } } @@ -993,7 +1012,7 @@ private function addCleanDataToXmlNode(\SimpleXMLElement $node, array $data): vo // Handle xml:lang in various forms (including double underscore from import service) foreach (['xml:lang', '_xml:lang', '_xml__lang', 'xml_lang'] as $langKey) { if (isset($value[$langKey])) { - $valueNode->addAttribute('xml:lang', $value[$langKey]); + $valueNode->addAttribute('xml:lang', $value[$langKey], 'http://www.w3.org/XML/1998/namespace'); break; } } @@ -1069,7 +1088,7 @@ private function addPropertiesToXml(\SimpleXMLElement $node, array $properties): // Add xml:lang if present in various forms (including double underscore from import service) foreach (['xml:lang', '_xml:lang', '_xml__lang', 'xml_lang'] as $langKey) { if (isset($property['value'][$langKey])) { - $valueNode->addAttribute('xml:lang', $property['value'][$langKey]); + $valueNode->addAttribute('xml:lang', $property['value'][$langKey], 'http://www.w3.org/XML/1998/namespace'); break; } } @@ -1111,7 +1130,7 @@ private function addModelMetadataToXml(\SimpleXMLElement $xml, array $modelMetad if (is_array($modelMetadata['name']) && isset($modelMetadata['name']['_value'])) { $nameNode[0] = (string)$modelMetadata['name']['_value']; if (isset($modelMetadata['name']['xml:lang'])) { - $nameNode->addAttribute('xml:lang', $modelMetadata['name']['xml:lang']); + $nameNode->addAttribute('xml:lang', $modelMetadata['name']['xml:lang'], 'http://www.w3.org/XML/1998/namespace'); } } elseif (is_string($modelMetadata['name'])) { $nameNode[0] = $modelMetadata['name']; @@ -1124,7 +1143,7 @@ private function addModelMetadataToXml(\SimpleXMLElement $xml, array $modelMetad if (is_array($modelMetadata['documentation']) && isset($modelMetadata['documentation']['_value'])) { $docNode[0] = (string)$modelMetadata['documentation']['_value']; if (isset($modelMetadata['documentation']['xml:lang'])) { - $docNode->addAttribute('xml:lang', $modelMetadata['documentation']['xml:lang']); + $docNode->addAttribute('xml:lang', $modelMetadata['documentation']['xml:lang'], 'http://www.w3.org/XML/1998/namespace'); } } elseif (is_string($modelMetadata['documentation'])) { $docNode[0] = $modelMetadata['documentation']; diff --git a/lib/Settings/softwarecatalogus_register.json b/lib/Settings/softwarecatalogus_register.json index a68c9b1c..1640e277 100644 --- a/lib/Settings/softwarecatalogus_register.json +++ b/lib/Settings/softwarecatalogus_register.json @@ -147,7 +147,10 @@ "groups": null, "authorization": null, "deleted": null, - "configuration": null + "configuration": { + "objectNameField": "naam", + "objectDescriptionField": "beschrijving" + } }, "voorziening": { "uri": null, @@ -1114,7 +1117,8 @@ }, "deleted": null, "configuration": { - "objectDescriptionField": "" + "objectNameField": "naam", + "objectDescriptionField": "beschrijving" } }, "contactpersoon": { @@ -1837,7 +1841,7 @@ "deleted": null, "configuration": { "objectNameField": "naam", - "objectDescriptionField": "beschrijvingKort" + "objectDescriptionField": "type" } }, "voorzieninggebruik": { @@ -2311,8 +2315,8 @@ "authorization": null, "deleted": null, "configuration": { - "objectNameField": "", - "objectDescriptionField": "" + "objectNameField": "organisatie", + "objectDescriptionField": "status" } }, "contract": { @@ -2815,7 +2819,10 @@ "groups": null, "authorization": null, "deleted": null, - "configuration": null + "configuration": { + "objectNameField": "type", + "objectDescriptionField": "beschrijvingKort" + } }, "beoordeeling": { "uri": null, @@ -5092,7 +5099,10 @@ "groups": null, "authorization": null, "deleted": null, - "configuration": null + "configuration": { + "objectNameField": "ondersteuntStandaardversie", + "objectDescriptionField": "moduleIsCompliant" + } }, "moduleGebruik": { "uri": null, @@ -5286,7 +5296,10 @@ "groups": null, "authorization": null, "deleted": null, - "configuration": null + "configuration": { + "objectNameField": "module", + "objectDescriptionField": "beschrijvingKort" + } } }, "objects": [] diff --git a/src/components/GenericObjectTable.vue b/src/components/GenericObjectTable.vue index aea79b0e..cfcd0dfd 100644 --- a/src/components/GenericObjectTable.vue +++ b/src/components/GenericObjectTable.vue @@ -179,25 +179,70 @@ import { objectStore, navigationStore } from '../store/store.js' - - - - - - - - - - - - - - - - -
{{ t('opencatalogi', 'Property') }}{{ t('opencatalogi', 'Value') }}{{ t('opencatalogi', 'Status') }}
{{ property.label }} - {{ property.value }} - {{ property.status }}
+ +
+

+ {{ getObjectSummary(item) }} +

+

+ {{ t('opencatalogi', 'No description available') }} +

+ + +
+ + {{ property.label }}: {{ property.value }} + +
+
+ +
+ + + + + + + + + + + + + + + + +
{{ t('opencatalogi', 'Property') }}{{ t('opencatalogi', 'Value') }}{{ t('opencatalogi', 'Status') }}
{{ property.label }} + {{ property.value }} + {{ property.status }}
+
+ +
+ +
+

+ {{ getObjectSummary(item) }} +

+

+ {{ t('opencatalogi', 'No description available') }} +

+
+ + + + + + + + + +
{{ property.label }} + {{ property.value }} +
+
@@ -512,6 +557,14 @@ export default { type: String, default: null, }, + /** + * Display mode for cards: 'properties' shows property table, 'description' shows description, 'mixed' shows both + */ + cardDisplayMode: { + type: String, + default: 'properties', + validator: value => ['properties', 'description', 'mixed'].includes(value), + }, }, data() { @@ -670,11 +723,26 @@ export default { }, getObjectTitle(item) { - return item?.title || item?.name || item?.['@self']?.name || this.getObjectId(item) || 'Unknown' + // For organizations, prioritize naam field which is the proper Dutch name field + if (this.objectType === 'organisatie' && item?.naam) { + return item.naam + } + + // For other objects or fallback, use the @self.name (which we fixed) or other fallbacks + return item?.title || item?.name || item?.naam || item?.['@self']?.name || this.getObjectId(item) || 'Unknown' }, getObjectSummary(item) { - return item?.summary || item?.description || '' + // For organizations, create a meaningful description from available fields + if (this.objectType === 'organisatie') { + if (item?.beschrijvingKort) return item.beschrijvingKort + if (item?.beschrijvingLang) return item.beschrijvingLang + if (item?.type && item?.naam) return `${item.type} organisatie` + if (item?.type) return item.type + } + + // For other object types, use standard fields + return item?.summary || item?.description || item?.beschrijvingKort || item?.beschrijvingLang || '' }, getColumnValue(item, column) { @@ -701,6 +769,15 @@ export default { })).filter(prop => prop.value !== 'N/A') }, + getKeyProperties(item) { + // Show only the first few most important properties in a compact format + return this.orderedEnabledColumns.slice(0, 3).map(column => ({ + key: column.key || column.id, + label: column.label, + value: this.getColumnValue(item, column), + })).filter(prop => prop.value !== 'N/A' && prop.value !== null && prop.value !== undefined) + }, + getActionDisabled(action) { if (typeof action.disabled === 'function') { return action.disabled() @@ -878,6 +955,54 @@ export default { min-width: 0; } +.cardDescription { + margin-top: 12px; +} + +.summaryText { + font-size: 14px; + line-height: 1.4; + color: var(--color-main-text); + margin: 0 0 12px 0; +} + +.noSummaryText { + font-size: 14px; + color: var(--color-text-lighter); + font-style: italic; + margin: 0 0 12px 0; +} + +.keyProperties { + display: flex; + flex-direction: column; + gap: 4px; + padding-top: 8px; + border-top: 1px solid var(--color-border-dark); +} + +.keyProperty { + font-size: 12px; + color: var(--color-main-text); +} + +.keyProperty strong { + color: var(--color-text-lighter); + font-weight: 600; +} + +.cardProperties { + margin-top: 12px; +} + +.cardMixed { + margin-top: 12px; +} + +.cardMixed .cardDescription { + margin-bottom: 12px; +} + .statisticsTable { width: 100%; border-collapse: collapse; @@ -897,6 +1022,21 @@ export default { font-size: 12px; } +.statisticsTable.compact { + font-size: 12px; +} + +.statisticsTable.compact td { + padding: 4px 8px; +} + +.truncatedText { + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .viewTableContainer { overflow-x: auto; border: 1px solid var(--color-border); diff --git a/src/main.js b/src/main.js index 11977c19..b5f6029e 100644 --- a/src/main.js +++ b/src/main.js @@ -2,7 +2,10 @@ import Vue from 'vue' import { PiniaVuePlugin } from 'pinia' import pinia from './pinia.js' import App from './App.vue' +import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip.js' + Vue.mixin({ methods: { t, n } }) +Vue.directive('tooltip', Tooltip) Vue.use(PiniaVuePlugin) diff --git a/src/navigation/MainMenu.vue b/src/navigation/MainMenu.vue index b638b1f1..cd7ebb9c 100644 --- a/src/navigation/MainMenu.vue +++ b/src/navigation/MainMenu.vue @@ -5,29 +5,27 @@ import { navigationStore } from '../store/store.js' + + + + diff --git a/src/views/contracten/ContractIndex.vue b/src/views/contracten/ContractIndex.vue new file mode 100644 index 00000000..8dd17110 --- /dev/null +++ b/src/views/contracten/ContractIndex.vue @@ -0,0 +1,250 @@ +/** + * ContractIndex.vue + * Component for displaying and managing contracten using GenericObjectTable + * @category Views + * @package softwarecatalog + * @author Ruben Linde + * @copyright 2024 + * @license AGPL-3.0-or-later + * @version 1.0.0 + * @link https://github.com/opencatalogi/softwarecatalog + */ + + + + + + diff --git a/src/views/organisaties/OrganisatieIndex.vue b/src/views/organisaties/OrganisatieIndex.vue index 5d87f922..a02b278d 100644 --- a/src/views/organisaties/OrganisatieIndex.vue +++ b/src/views/organisaties/OrganisatieIndex.vue @@ -28,6 +28,7 @@ import { navigationStore, objectStore } from '../../store/store.js' :actions="organisatieActions" :add-action="addOrganisatieAction" :help-url="'https://conduction.gitbook.io/softwarecatalog-nextcloud/beheerders/organisaties'" + card-display-mode="description" @mounted="onMounted" /> @@ -54,9 +55,9 @@ export default { return { organisatieProperties: [ { - id: 'name', - label: 'Name', - key: 'name', + id: 'naam', + label: 'Naam', + key: 'naam', sortable: true, searchable: true, }, @@ -68,9 +69,16 @@ export default { searchable: true, }, { - id: 'summary', - label: 'Summary', - key: 'summary', + id: 'type', + label: 'Type', + key: 'type', + sortable: true, + searchable: true, + }, + { + id: 'beschrijvingKort', + label: 'Korte beschrijving', + key: 'beschrijvingKort', sortable: false, searchable: true, }, @@ -95,13 +103,6 @@ export default { sortable: true, searchable: true, }, - { - id: 'updatedAt', - label: 'Last Updated', - key: 'updatedAt', - sortable: true, - searchable: false, - }, ], organisatieObjectActions: [ { @@ -223,9 +224,26 @@ export default { } }, methods: { - onMounted() { - console.info('OrganisatieIndex mounted, fetching organisaties...') - objectStore.fetchCollection('organisatie') + /** + * Handle component mount - initialize settings and fetch organisaties + * @return {Promise} + */ + async onMounted() { + console.info('OrganisatieIndex mounted, initializing...') + try { + // Ensure settings are loaded first (this will also register object types) + if (!objectStore.settings) { + console.info('Loading settings before fetching organisaties...') + await objectStore.fetchSettings() + } + + // Fetch organisaties collection + console.info('Fetching organisaties...') + await objectStore.fetchCollection('organisatie') + } catch (error) { + console.error('Error initializing OrganisatieIndex:', error) + // Show error to user if needed + } }, }, } diff --git a/src/views/voorzieningen/VoorzieningIndex.vue b/src/views/voorzieningen/VoorzieningIndex.vue new file mode 100644 index 00000000..34e54113 --- /dev/null +++ b/src/views/voorzieningen/VoorzieningIndex.vue @@ -0,0 +1,243 @@ +/** + * VoorzieningIndex.vue + * Component for displaying and managing voorzieningen using GenericObjectTable + * @category Views + * @package softwarecatalog + * @author Ruben Linde + * @copyright 2024 + * @license AGPL-3.0-or-later + * @version 1.0.0 + * @link https://github.com/opencatalogi/softwarecatalog + */ + + + + + + From 77dcb10ab46cd1819145f8d77aba1676a7b12e27 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 13 Aug 2025 13:07:39 +0200 Subject: [PATCH 2/3] Lets start our work on our custom organisation card --- src/components/GenericObjectTable.vue | 19 +- src/components/cards/OrganisatieCard.vue | 337 ++++++++++++++++++++ src/views/organisaties/OrganisatieIndex.vue | 3 + 3 files changed, 358 insertions(+), 1 deletion(-) create mode 100644 src/components/cards/OrganisatieCard.vue diff --git a/src/components/GenericObjectTable.vue b/src/components/GenericObjectTable.vue index cfcd0dfd..43c1b49b 100644 --- a/src/components/GenericObjectTable.vue +++ b/src/components/GenericObjectTable.vue @@ -156,7 +156,17 @@ import { objectStore, navigationStore } from '../store/store.js'
@@ -52,6 +53,7 @@ export default { name: 'OrganisatieIndex', components: { GenericObjectTable, + // eslint-disable-next-line vue/no-unused-components OrganisatieCard, }, data() { @@ -215,6 +217,17 @@ export default { }, }, ], + organisatieFilters: [ + { + key: 'status', + label: 'Status', + options: [ + { value: 'all', label: 'Alle statussen' }, + { value: 'Actief', label: 'Actief' }, + { value: 'concept', label: 'Concept' }, + ], + }, + ], addOrganisatieAction: { id: 'add', label: 'Add Organisatie',