import { useCallback, useEffect, useMemo, useState } from 'react'; import { RetainedAppState, useApp } from './AppProvider'; import { WORKSPACE_BAND_ID } from './workspace-band'; import { DataHub } from '../features/project-import/DataHub'; import { DashboardPanel } from '../features/dashboard/DashboardPanel'; import { DbStructurePanel } from '../features/db-structure/DbStructurePanel'; import { ResultsBrowser } from '../features/results-browser/ResultsBrowser'; import { SearchFilterBar, SearchTextControl, SheetAndSearchControls, WorkTimeslotControl } from '../features/search-filter/SearchFilterBar'; import { useFilteredRecords } from '../features/search-filter/use-filtered-records'; import { DatasetViewer } from '../features/dataset-viewer/DatasetViewer'; import { ObsidianGraphView } from '../features/mitre-graph/ObsidianGraphView'; import { ActionlistControls } from '../features/actionlist/ActionlistControls'; import { ImportWorkflowDialog } from '../features/import-workflow/ImportWorkflowDialog'; import { RESULT_DOCK_SLOT_ID } from '../features/linked-results/result-window-layout'; import { rowsToSpreadsheetSafeCsv } from '../exports/csv-export'; import { downloadText } from '../exports/text-download'; import { exportSnapshot } from '../exports/snapshot-export'; import { browserDownloadAdapter } from '../platform/browser/browser-download-adapter'; import assessorLogo from '../assets/branding/assessor-icon-transparent.png'; import { TriangleIcon } from '../components/TriangleIcon'; import { SidebarSeverityMetrics } from '../features/sidebar/SidebarSeverityMetrics'; import { ViewBookmarks } from '../features/view-bookmarks/ViewBookmarks'; import { pathSnapshotFindingIds, type PathSnapshotState, type ViewBookmark } from '../features/view-bookmarks/view-bookmarks'; import { PathfinderFilterTrail } from '../features/mitre-graph/PathfinderFilterTrail'; import { ASSESSOR_PRODUCT_VERSION } from '../domain/assessor-release'; import { hasSelectedTimeslotScope } from './work-scope'; import { AboutDialog } from '../features/about/AboutDialog'; type AppArea = 'db-structure' | 'data-hub' | 'dashboard' | 'graph' | 'work' | 'results'; function AreaIcon({ area }: { area: AppArea }) { if (area === 'dashboard') return ; if (area === 'db-structure') return ; if (area === 'data-hub') return ; if (area === 'graph') return ; if (area === 'results') return ; return ; } const AREA_TOPBAR_TITLE: Record = { 'db-structure': 'DB Structure', 'data-hub': 'Data Hub', dashboard: 'Client Info', graph: 'Pathfinder', work: 'Work', results: 'Result DB' }; /* Reihenfolge der Hauptnavigation. Der erste Eintrag liegt auf der Oberkante des Panels und geht dort farblich in es ueber; nur an dieser einen Ecke kann das Panel nicht gerundet sein. Die Liste steht deshalb an einer Stelle, damit die Ecke der Reihenfolge folgt und nicht einem Bereichsnamen im CSS. */ const NAV_AREAS = [ ['db-structure', 'DB Structure'], ['data-hub', 'Datahub'], ['dashboard', 'Client Info'], ['work', 'Work'], ['results', 'Result DB'], ['graph', 'Pathfinder'] ] as const satisfies readonly (readonly [AppArea, string])[]; export function App() { const app = useApp(); const [activeArea, setActiveArea] = useState('data-hub'); const [activeProjectName, setActiveProjectName] = useState(); const [dataHubClientView, setDataHubClientView] = useState(false); const [dataHubDetailClientName, setDataHubDetailClientName] = useState(); const [pathSnapshotRequest, setPathSnapshotRequest] = useState<{ id: string; path: PathSnapshotState }>(); const [aboutOpen, setAboutOpen] = useState(false); const [visitedGraphScope, setVisitedGraphScope] = useState(); const graphVisited = visitedGraphScope === app.workScopeId; useEffect(() => { if (activeArea === 'graph') setVisitedGraphScope(app.workScopeId); }, [activeArea, app.workScopeId]); const clearPathSnapshotRequest = useCallback(() => setPathSnapshotRequest(undefined), []); const { dataset, activeSheet, reviews, snapshot, theme, setTheme, delimiter, setDelimiter, view, setView, actionOnly, sidebarCollapsed, toggleSidebar, toggleWorkspace, backendStatus, targetProjectId } = app; const openWork = useCallback(() => { setView('records'); setActiveArea('work'); }, [setView]); const marked = useMemo(() => Object.values(reviews).filter(review => review.include).length, [reviews]); const visible = useFilteredRecords().length; const backendLabel = backendStatus === 'connected' ? 'Server connection available' : backendStatus === 'checking' ? 'Checking server connection' : 'No server connection'; const hasClientTimeslotScope = !targetProjectId || hasSelectedTimeslotScope(app.activeScanRunIds, app.activeAssessmentIds); const headerClientName = (activeArea === 'data-hub' && dataHubClientView ? dataHubDetailClientName : activeProjectName)?.trim(); const handleDataHubDetailViewChanged = useCallback((isDetailView: boolean, clientName?: string) => { setDataHubClientView(isDetailView); setDataHubDetailClientName(isDetailView ? clientName : undefined); }, []); const connectionIndicator =