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
;
}
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
{state.label}
{actionable
?
: shown(state.value)}
;
})}
Mappable deviations
{shown(total)}
Shows how far mapping has progressed, not whether controls are met.
;
}
interface FootprintDetail { id: string; group: string; title?: string; via?: string; findings: number; reachable: boolean }
/** Shared footprint frame: hover or focus names a cell in the detail line below the grid. */
function FootprintFrame({ title, glyph, action, children }: { title: string; glyph: 'shield' | 'policy'; action?: ReactNode; children: ReactNode }) {
return
{title}
{action}
{children}
;
}
/** Hover or focus names a cell here, below the grid. */
function FootprintDetailLine({ current }: { current?: FootprintDetail }) {
return
;
}
interface BsiGridProps {
practices: BsiFootprint['practices'];
findingsOf: Map>;
derived: Map;
onOpenRequirement(requirementId: string): void;
onOpenPractice(practiceId: string): void;
}
/**
* BSI Kernel requirements by practice in catalog order, each a square labelled with its number inside the practice.
* `hits` (the tile) shows the two practices with the most findings; `all` (the Show all window) shows every practice, larger. A
* requirement the BSI mapping cannot reach from any ISO control is marked, so an empty square never reads as a gap of this client.
*/
function BsiRequirementGrid({ mode, practices, findingsOf, derived, onOpenRequirement, onOpenPractice }: BsiGridProps & { mode: 'hits' | 'all' }) {
const [current, setCurrent] = useState();
const blocks = practices.map(practice => {
const findings = new Set();
const hits = practice.requirements.filter(requirement => {
const list = findingsOf.get(requirement.id) ?? [];
for (const finding of list) findings.add(finding.resultProjectionId);
return list.length > 0;
});
return { practice, findings: findings.size, cells: practice.requirements, hits: hits.length };
});
// The tile keeps the practices with the most findings, like the BSI tile; ties stay in catalog order.
if (mode === 'hits') blocks.splice(0, blocks.length, ...blocks.filter(block => block.hits > 0)
.sort((left, right) => right.findings - left.findings).slice(0, BSI_TILE_PRACTICES));
if (!blocks.length) return
No BSI requirements derived from accepted ISO controls in this timeslot.