'use client' import React, { useState, useEffect } from 'react' import { ControlListView } from '../control-library/components/ControlListView' import { useControlLibraryState } from '../control-library/components/useControlLibraryState' /** * Master Controls page — reuses the Control Library UI exactly, * but shows Master Controls (13.5K grouped controls) instead of * individual atomic controls (272K). * * The MC API route (/api/sdk/v1/master-controls) returns data in * the same format as the canonical controls endpoint. */ export default function MasterControlsPage() { // Reuse the exact same state hook — it fetches from BACKEND_URL // We override BACKEND_URL via a wrapper, but for now we reuse as-is // since both endpoints speak the same format. const state = useControlLibraryState('/api/sdk/v1/master-controls') if (state.loading && state.controls.length === 0) { return (
) } if (state.error) { return (

{state.error}

) } // DETAIL mode — show MC members if (state.mode === 'detail' && state.selectedControl) { return ( { state.setMode('list'); state.setSelectedControl(null) }} /> ) } // LIST mode — exact same UI as Control Library return ( {}} setCurrentPage={state.setCurrentPage} onSelectControl={(ctrl) => { state.setSelectedControl(ctrl); state.setMode('detail') }} onCreateMode={() => {}} onEnterReview={() => {}} onBulkReject={async () => {}} onRefresh={() => { state.loadControls(); state.loadMeta() }} onLoadStats={state.loadProcessedStats} onFullReload={state.fullReload} /> ) } // ── MC Detail Panel ───────────────────────────────────────────── interface Member { control_id: string title: string severity: string phase: string action: string regulation_source?: string regulation_article?: string } const SEV = { critical: 'bg-red-100 text-red-800', high: 'bg-orange-100 text-orange-800', medium: 'bg-yellow-100 text-yellow-800', low: 'bg-blue-100 text-blue-800', } as Record function MCDetail({ mc, onBack }: { mc: Record; onBack: () => void }) { const [members, setMembers] = useState([]) const [loading, setLoading] = useState(true) const [phaseFilter, setPhaseFilter] = useState('') const mcId = (mc.control_id || mc.master_control_id || '') as string const mcName = (mc.title || mc.canonical_name || '') as string const totalControls = (mc.total_controls || 0) as number const phases = (mc.phases_covered || []) as string[] useEffect(() => { setLoading(true) fetch(`/api/sdk/v1/master-controls?endpoint=control&id=${mcId}`) .then(r => r.ok ? r.json() : null) .then(data => { if (data?.members) setMembers(data.members) else if (data?.requirements) { // Fallback: parse requirements strings setMembers((data.requirements as string[]).map((req: string) => { const match = req.match(/^\[(\w+)\]\s+(\S+):\s+(.+)$/) return match ? { control_id: match[2], title: match[3], phase: match[1], action: '', severity: '' } : { control_id: '', title: req, phase: '', action: '', severity: '' } })) } }) .catch(() => {}) .finally(() => setLoading(false)) }, [mcId]) const filtered = phaseFilter ? members.filter(m => m.phase === phaseFilter) : members const uniquePhases = [...new Set(members.map(m => m.phase).filter(Boolean))] const phaseGroups = uniquePhases.reduce((acc, p) => { acc[p] = members.filter(m => m.phase === p).length return acc }, {} as Record) return (
{/* Header */}

{mcName}

{mcId} — {totalControls} Atomic Controls

{/* Phase badges */}
{uniquePhases.map(p => ( ))} {phaseFilter && ( )}
{/* Members */}
{filtered.length} von {members.length} Controls{phaseFilter ? ` (Phase: ${phaseFilter})` : ''}
{loading ? (
) : (
{filtered.map((m, i) => { const inner = ( <>
{m.control_id} {m.severity && ( {m.severity} )} {m.phase && ( {m.phase} )} {m.action && ( {m.action} )}

{m.title}

{m.regulation_source && (

{m.regulation_source} {m.regulation_article}

)} ) return m.control_id ? ( {inner} ) : (
{inner}
) })} {filtered.length === 0 && !loading && (
Keine Controls gefunden
)}
)}
) }