/** * PORTA architecture overview — data → processing → organisation. * Layout matches local/notes/demo-tue-porta_architecture_v3.html (order: Schicht 3 → Pfeil ↓ → Schicht 2 → Pfeil ↑ → Schicht 1). */ import React, { useMemo } from 'react'; import { useLanguage } from '../providers/language/LanguageContext'; import { useIntegrationsOverview, type DataLayerItem, type LiveStats } from '../hooks/useIntegrationsOverview'; import styles from './IntegrationsOverview.module.css'; /** de-CH: 1'234'567 */ function _formatStatNumber(n: number): string { return new Intl.NumberFormat('de-CH', { maximumFractionDigits: 0 }).format(n); } function _shortExtractorSymbol(className: string): string { return className.replace(/Extractor$/i, '') || className; } function _shortRendererSymbol(className: string): string { return className.replace(/^Renderer/i, '') || className; } function _IconLightning({ className }: { className?: string }) { return ( ); } function _IconGear({ className }: { className?: string }) { return ( ); } function _ArrowDown() { return (
); } function _ArrowUp() { return (
); } function _authorityIcon(authority?: string): string { const a = (authority || '').toLowerCase(); if (a === 'msft') return 'Ⓜ'; if (a === 'google') return 'G'; if (a === 'clickup') return '▣'; if (a === 'local') return '●'; return '◇'; } function _dataLayerItemKey(item: DataLayerItem): string { return `${item.kind}-${item.id}`; } /** i18n for provider labels where the API sends a fixed German string (e.g. Tavily suffix). */ function _aicoreConnectorLabel( connectorType: string, rawLabel: string, t: (key: string) => string, ): string { if (connectorType === 'tavily') { return `Tavily (${t('Websuche')})`; } return rawLabel; } function _renderPersonalChip( item: DataLayerItem, stylesModule: typeof styles, ): React.ReactElement { return (
{_authorityIcon(item.authority)}
{item.displayLabel}
{item.connectionReference}
); } interface _CorporateInstanceGroup { instanceId: string; instanceLabel: string; featureCode: string; } function _groupCorporateByInstance(items: DataLayerItem[]): _CorporateInstanceGroup[] { const map = new Map(); for (const item of items) { const iid = item.featureInstanceId || '_unknown'; if (map.has(iid)) { const group = map.get(iid)!; if (item.instanceLabel && (!group.instanceLabel || group.instanceLabel === group.featureCode)) { group.instanceLabel = item.instanceLabel.trim(); } if (item.featureCode && !group.featureCode) { group.featureCode = item.featureCode; } continue; } map.set(iid, { instanceId: iid, instanceLabel: (item.instanceLabel || '').trim(), featureCode: item.featureCode || item.connectorType || '', }); } return Array.from(map.values()); } function _ArrowRight() { return (
); } export const IntegrationsOverviewPage: React.FC = () => { const { t } = useLanguage(); const { loading, error, diagram, mandateCards, workflowChips, hasNeutralization, refetch, } = useIntegrationsOverview(); const infraToolRows = useMemo(() => { const tools = diagram?.infraTools ?? []; return tools.map((row) => ({ ...row, label: t(row.label) })); }, [diagram?.infraTools, t]); const statItems = useMemo(() => { const s: LiveStats = diagram?.liveStats ?? { aiCallCount: 0, aiCallPeriodDays: 30, totalWorkflows: 0, activeWorkflows: 0, totalRuns: 0, totalTokens: 0, }; const connectedSystems = (diagram?.dataLayerItems ?? []) .filter((d) => d.kind === 'userConnection').length; return [ { value: s.aiCallCount, label: t('AI-Aufrufe'), sub: `${s.aiCallPeriodDays} ${t('Tage')}` }, { value: s.activeWorkflows, label: t('Aktive Workflows'), sub: s.totalWorkflows > 0 ? `${_formatStatNumber(s.totalWorkflows)} ${t('total')}` : undefined }, { value: s.totalRuns, label: t('Workflow-Läufe'), sub: s.totalTokens > 0 ? `${_formatStatNumber(s.totalTokens)} ${t('Tokens')}` : undefined }, { value: connectedSystems, label: t('Verbundene Systeme') }, ]; }, [diagram, t]); const dataPersonalItems = useMemo( () => (diagram?.dataLayerItems ?? []).filter((d) => d.kind === 'userConnection'), [diagram?.dataLayerItems], ); const corporateGroups = useMemo(() => { const items = (diagram?.dataLayerItems ?? []).filter( (d) => d.kind !== 'userConnection' && d.kind !== 'dataSource', ); return _groupCorporateByInstance(items); }, [diagram?.dataLayerItems]); return (

{t('Integrationen')}

{t('PORTA Architektur — Daten, Verarbeitung und Mandanten auf einen Blick.')}

{t('PORTA Architektur v3: Drei separate Boxen in Schicht 2 — Infrastruktur, PORTA, Nutzen')}

{loading &&
{t('Laden…')}
} {error && (
{error}{' '}
)} {!loading && !error && ( <>
3 {t('Organisation — Mandanten & Module')}
{mandateCards.length === 0 ? (

{t('Keine Mandanten in der Navigation sichtbar.')}

) : ( mandateCards.map((m) => (
{m.uiLabel}
{m.moduleChips.map((chip) => ( {chip} ))}
)) )}
<_ArrowDown />
2 {t('Verarbeitung — Infrastruktur → PORTA → Nutzen')}
{t('Infrastruktur')}
<_IconLightning className={styles.infraTitleSvg} /> {t('AI LLM')}
{(diagram?.aicoreModules ?? []).map((m) => (
{_aicoreConnectorLabel(m.connectorType, m.label, t)}
{m.modelCount > 0 ? (
{m.modelCount} {t('Modelle')}
) : null}
))}
<_IconGear className={styles.infraTitleSvg} /> {t('Werkzeuge')}
{infraToolRows.length > 0 ? ( infraToolRows.map((ex) => (
<_IconGear className={styles.infraItemGear} /> {ex.label}
)) ) : (
{t('Keine Werkzeuge registriert.')}
)}
<_ArrowRight />
{t('PORTA')}
🛡 {t('Neutralisierung')}
{t('PII-Masking')} {t('Private LLM')} {t('Platzhalter')}
{!hasNeutralization && (
{t('optional pro Instanz')}
)}
🔒 {t('Datenkontrolle')}
{t('RBAC')} {t('Mandanten')} {t('Rollen')}
{t('Workflows')}
{workflowChips.length === 0 ? (
{t('Keine Workflows aus Graphical Editor geladen.')}
) : (
{workflowChips.map((w) => (
{w}
))}
)}
{t('Extractors')}
{(diagram?.extractorClasses ?? []).length > 0 ? (diagram?.extractorClasses ?? []).map((row) => ( {_shortExtractorSymbol(row.className)} )) : (diagram?.extractorExtensions ?? []).map((b) => ( {b} ))}
{t('Renderers')}
{(diagram?.rendererClasses ?? []).length > 0 ? (diagram?.rendererClasses ?? []).map((row) => ( {_shortRendererSymbol(row.className)} )) : (diagram?.rendererFormats ?? []).map((b) => ( {b} ))}
<_ArrowRight />
{t('Nutzen')}
{statItems.map((item) => (
{typeof item.value === 'number' ? _formatStatNumber(item.value) : item.value}
{item.label} {item.sub ? ( {item.sub} ) : null}
))}
+ {t('Ihre KPIs — individuell konfigurierbar')}
<_ArrowUp />
1 {t('Daten — die Basis von allem')}
{t('Persönliche Verbindungen')}
{dataPersonalItems.length === 0 ? ( {t('Keine persönlichen Verbindungen.')} ) : (
{dataPersonalItems.map((item) => _renderPersonalChip(item, styles))}
)}
{t('Unternehmens- & Systemdaten')}
{corporateGroups.length === 0 ? ( {t('Keine Unternehmens- oder Systemdaten erfasst.')} ) : (
{corporateGroups.map((g) => ( {g.instanceLabel}{g.featureCode ? ` (${g.featureCode})` : ''} ))}
)}
)}
); };