Improve Team-indeling dashboard UI and cache invalidation

- Replace 'TEAM' label with Type attribute (Business/Enabling/Staf) in team blocks
- Make Type labels larger (text-sm) and brighter colors
- Make SUBTEAM label less bright (indigo-300) and smaller (text-[10px])
- Add 'FTE' suffix to bandbreedte values in header and application blocks
- Add Platform and Connected Device labels to application blocks
- Show Platform FTE and Workloads FTE separately in Platform blocks
- Add spacing between Regiemodel letter and count value
- Add cache invalidation for Team Dashboard when applications are updated
- Enrich team references with Type attribute in getSubteamToTeamMapping
This commit is contained in:
2026-01-10 02:16:55 +01:00
parent ea1c84262c
commit ca21b9538d
54 changed files with 13444 additions and 1789 deletions

View File

@@ -0,0 +1,151 @@
import { Router } from 'express';
import { OBJECT_TYPES, SCHEMA_GENERATED_AT, SCHEMA_OBJECT_TYPE_COUNT, SCHEMA_TOTAL_ATTRIBUTES } from '../generated/jira-schema.js';
import type { ObjectTypeDefinition, AttributeDefinition } from '../generated/jira-schema.js';
const router = Router();
// Extended types for API response
interface ObjectTypeWithLinks extends ObjectTypeDefinition {
incomingLinks: Array<{
fromType: string;
fromTypeName: string;
attributeName: string;
isMultiple: boolean;
}>;
outgoingLinks: Array<{
toType: string;
toTypeName: string;
attributeName: string;
isMultiple: boolean;
}>;
}
interface SchemaResponse {
metadata: {
generatedAt: string;
objectTypeCount: number;
totalAttributes: number;
};
objectTypes: Record<string, ObjectTypeWithLinks>;
}
/**
* GET /api/schema
* Returns the complete Jira Assets schema with object types, attributes, and links
*/
router.get('/', (req, res) => {
try {
// Build links between object types
const objectTypesWithLinks: Record<string, ObjectTypeWithLinks> = {};
// First pass: convert all object types
for (const [typeName, typeDef] of Object.entries(OBJECT_TYPES)) {
objectTypesWithLinks[typeName] = {
...typeDef,
incomingLinks: [],
outgoingLinks: [],
};
}
// Second pass: build link relationships
for (const [typeName, typeDef] of Object.entries(OBJECT_TYPES)) {
for (const attr of typeDef.attributes) {
if (attr.type === 'reference' && attr.referenceTypeName) {
// Add outgoing link from this type
objectTypesWithLinks[typeName].outgoingLinks.push({
toType: attr.referenceTypeName,
toTypeName: OBJECT_TYPES[attr.referenceTypeName]?.name || attr.referenceTypeName,
attributeName: attr.name,
isMultiple: attr.isMultiple,
});
// Add incoming link to the referenced type
if (objectTypesWithLinks[attr.referenceTypeName]) {
objectTypesWithLinks[attr.referenceTypeName].incomingLinks.push({
fromType: typeName,
fromTypeName: typeDef.name,
attributeName: attr.name,
isMultiple: attr.isMultiple,
});
}
}
}
}
const response: SchemaResponse = {
metadata: {
generatedAt: SCHEMA_GENERATED_AT,
objectTypeCount: SCHEMA_OBJECT_TYPE_COUNT,
totalAttributes: SCHEMA_TOTAL_ATTRIBUTES,
},
objectTypes: objectTypesWithLinks,
};
res.json(response);
} catch (error) {
console.error('Failed to get schema:', error);
res.status(500).json({ error: 'Failed to get schema' });
}
});
/**
* GET /api/schema/object-type/:typeName
* Returns details for a specific object type
*/
router.get('/object-type/:typeName', (req, res) => {
const { typeName } = req.params;
const typeDef = OBJECT_TYPES[typeName];
if (!typeDef) {
return res.status(404).json({ error: `Object type '${typeName}' not found` });
}
// Build links for this specific type
const incomingLinks: Array<{
fromType: string;
fromTypeName: string;
attributeName: string;
isMultiple: boolean;
}> = [];
const outgoingLinks: Array<{
toType: string;
toTypeName: string;
attributeName: string;
isMultiple: boolean;
}> = [];
// Outgoing links from this type
for (const attr of typeDef.attributes) {
if (attr.type === 'reference' && attr.referenceTypeName) {
outgoingLinks.push({
toType: attr.referenceTypeName,
toTypeName: OBJECT_TYPES[attr.referenceTypeName]?.name || attr.referenceTypeName,
attributeName: attr.name,
isMultiple: attr.isMultiple,
});
}
}
// Incoming links from other types
for (const [otherTypeName, otherTypeDef] of Object.entries(OBJECT_TYPES)) {
for (const attr of otherTypeDef.attributes) {
if (attr.type === 'reference' && attr.referenceTypeName === typeName) {
incomingLinks.push({
fromType: otherTypeName,
fromTypeName: otherTypeDef.name,
attributeName: attr.name,
isMultiple: attr.isMultiple,
});
}
}
}
res.json({
...typeDef,
incomingLinks,
outgoingLinks,
});
});
export default router;