import { useState, type ReactNode } from 'react'; import { DonutSmallArcMarker, donutArcPath, donutPercentageLabel } from '../../components/DonutMetricCard/donut-arc'; import { DashboardGlyph } from '../../features/dashboard/ClientTileParts'; import { EnvironmentResultsButton } from '../../features/dashboard/EnvironmentResultsButton'; import { ISO_MAPPING_FAILED, ISO_MAPPING_NO_BSI, type ComplianceMappingState } from './compliance-work-facets'; import { bsiRequirementFindings, type BsiFootprint, type BsiOverview } from '../bsi-grundschutz'; import { ANNEX_A_CONTROL_COUNTS, ANNEX_A_THEME_CLAUSES, type RegulatoryControlOverviewItem, type RegulatoryControlTheme, type RegulatoryMappingCoverage } from '../regulatory-mapping'; export type ComplianceFramework = 'iso' | 'bsi'; // People and Physical share one column, so the footprint stays three columns wide. const COLUMNS: RegulatoryControlTheme[][] = [['Organizational'], ['People', 'Physical'], ['Technological']]; export const COVERAGE_TILE_TITLE = 'Mapping coverage'; export const FOOTPRINT_TILE_TITLE = 'Annex A footprint'; export const BSI_FOOTPRINT_TILE_TITLE = 'Kernel footprint'; export const BSI_FOOTPRINT_WINDOW_TITLE = 'BSI Grundschutz++ Kernel footprint'; /** Practices the BSI footprint tile shows; `Show all` shows every practice. */ const BSI_TILE_PRACTICES = 2; const FRAMEWORKS: Array<[ComplianceFramework, string]> = [['iso', 'ISO 27001/27002'], ['bsi', 'BSI Grundschutz++']]; const number = (value: number) => value.toLocaleString('en-US'); const findingCount = (value: number) => `${number(value)} finding${value === 1 ? '' : 's'}`; /** Shading steps of the footprint; a control or requirement without findings stays unshaded. */ export const footprintStep = (findings: number) => findings >= 25 ? 4 : findings >= 10 ? 3 : findings >= 3 ? 2 : findings >= 1 ? 1 : 0; /** Switches the overview row between the frameworks; the framework tiles below stay as they are. */ export function ComplianceFrameworkSwitch({ value, onChange }: { value: ComplianceFramework; onChange(value: ComplianceFramework): void }) { // The Client Info tab buttons: the approved selection control of this view. return
{FRAMEWORKS.map(([id, label]) => )}
; } interface Props { framework: ComplianceFramework; /** Undefined while the overview loads: values stay an em dash and nothing opens Work. */ controls?: RegulatoryControlOverviewItem[]; coverage?: RegulatoryMappingCoverage; bsi?: BsiOverview; bsiFootprint?: BsiFootprint; /** Annex A titles of the active corpus; a control without findings is named by these. */ annexATitles?: Record; onOpenControl(controlId: string): void; onOpenTheme(theme: RegulatoryControlTheme): void; onOpenBsiRequirement(requirementId: string): void; onOpenBsiPractice(practiceId: string): void; /** Opens Work with the deviations in this `ISO mapping` state (failed, to map them again, or without a BSI requirement). */ onOpenMappingState(state: ComplianceMappingState): void; } /** * Client Info `Compliance` overview row above the framework tiles, for the framework chosen by the switch: how far * the timeslot's deviations are mapped, and where they land in the catalog. It states where findings land, * never whether a control or requirement is met. */ export function ComplianceOverview(props: Props) { const { framework, controls, coverage, bsi, onOpenMappingState } = props; const ready = controls && coverage ? coverage : undefined; if (framework === 'iso') { return
onOpenMappingState(ISO_MAPPING_FAILED) }]}/>
; } // A finding with a derived requirement also has an accepted ISO control; the rest of those keep only ISO. const withBsi = ready && controls ? new Set([...bsiRequirementFindings(controls, bsi).values()].flat().map(finding => finding.resultProjectionId)).size : undefined; return
onOpenMappingState(ISO_MAPPING_NO_BSI) }, { key: 'mappedNoneAccepted', label: 'Mapped, none accepted', value: ready?.mappedNoneAccepted, arc: true }, { key: 'notMapped', label: 'Not mapped yet', value: ready?.notMapped }, { key: 'failed', label: 'Mapping failed', value: ready?.failed, arc: true, onOpen: () => onOpenMappingState(ISO_MAPPING_FAILED) }]}/>
; } interface CoverageState { key: string; label: string; value?: number; arc?: boolean; onOpen?(): void } /** The shared Client Info donut; states without an arc are the uncovered remainder drawn by the neutral track. */ function CoverageTile({ caption, centre, total, states }: { caption: string; centre?: number; total?: number; states: CoverageState[] }) { const share = (value: number) => total ? value / total * 100 : 0; let offset = 0; const arcs = states.filter(state => state.arc && state.value).map(state => { const percentage = share(state.value!); const arcOffset = offset; offset += percentage; return ; }); const shown = (value?: number) => value === undefined ? '—' : number(value); return

{COVERAGE_TILE_TITLE}

{shown(centre)}{centre !== undefined && total ? donutPercentageLabel(share(centre)) : '—'}
{caption}
{states.map(state => { // A state with an action and findings is one click target over its whole row, like an ISO tile theme. const actionable = Boolean(state.onOpen && state.value); return