'use client' import { useState, useMemo, useRef } from 'react' import { apiModules } from '@/lib/sdk/api-docs/endpoints' import type { HttpMethod, BackendService, ApiExposure } from '@/lib/sdk/api-docs/types' const METHOD_COLORS: Record = { GET: 'bg-green-100 text-green-800', POST: 'bg-blue-100 text-blue-800', PUT: 'bg-yellow-100 text-yellow-800', DELETE: 'bg-red-100 text-red-800', PATCH: 'bg-purple-100 text-purple-800', } const EXPOSURE_CONFIG: Record = { public: { label: 'Oeffentlich', color: 'bg-green-100 text-green-800 border-green-200', description: 'Internet-exponiert' }, partner: { label: 'Integration', color: 'bg-blue-100 text-blue-800 border-blue-200', description: 'API-Key/OAuth' }, internal: { label: 'Intern', color: 'bg-gray-100 text-gray-700 border-gray-200', description: 'Nur Admin' }, admin: { label: 'Wartung', color: 'bg-orange-100 text-orange-800 border-orange-200', description: 'Nur Setup' }, } type ServiceFilter = 'all' | BackendService type ExposureFilter = 'all' | ApiExposure function ExposureBadge({ exposure }: { exposure: ApiExposure }) { const config = EXPOSURE_CONFIG[exposure] return ( {config.label} ) } export default function ApiDocsPage() { const [search, setSearch] = useState('') const [serviceFilter, setServiceFilter] = useState('all') const [methodFilter, setMethodFilter] = useState('all') const [exposureFilter, setExposureFilter] = useState('all') const [expandedModules, setExpandedModules] = useState>(new Set()) const moduleRefs = useRef>({}) const filteredModules = useMemo(() => { const q = search.toLowerCase() return apiModules .filter((m) => serviceFilter === 'all' || m.service === serviceFilter) .filter((m) => { if (exposureFilter === 'all') return true if (m.exposure === exposureFilter) return true return m.endpoints.some((e) => (e.exposure || m.exposure) === exposureFilter) }) .map((m) => { const eps = m.endpoints.filter((e) => { if (methodFilter !== 'all' && e.method !== methodFilter) return false if (exposureFilter !== 'all' && (e.exposure || m.exposure) !== exposureFilter) return false if (!q) return true return ( e.path.toLowerCase().includes(q) || e.description.toLowerCase().includes(q) || m.name.toLowerCase().includes(q) || m.id.toLowerCase().includes(q) ) }) return { ...m, endpoints: eps } }) .filter((m) => m.endpoints.length > 0) }, [search, serviceFilter, methodFilter, exposureFilter]) const stats = useMemo(() => { const total = apiModules.reduce((s, m) => s + m.endpoints.length, 0) const python = apiModules.filter((m) => m.service === 'python').reduce((s, m) => s + m.endpoints.length, 0) const go = apiModules.filter((m) => m.service === 'go').reduce((s, m) => s + m.endpoints.length, 0) const exposureCounts = { public: 0, partner: 0, internal: 0, admin: 0 } apiModules.forEach((m) => { m.endpoints.forEach((e) => { const exp = e.exposure || m.exposure exposureCounts[exp]++ }) }) return { total, python, go, modules: apiModules.length, exposure: exposureCounts } }, []) const filteredTotal = filteredModules.reduce((s, m) => s + m.endpoints.length, 0) const toggleModule = (id: string) => { setExpandedModules((prev) => { const next = new Set(prev) if (next.has(id)) next.delete(id) else next.add(id) return next }) } const expandAll = () => setExpandedModules(new Set(filteredModules.map((m) => m.id))) const collapseAll = () => setExpandedModules(new Set()) const scrollToModule = (id: string) => { setExpandedModules((prev) => new Set([...prev, id])) setTimeout(() => { moduleRefs.current[id]?.scrollIntoView({ behavior: 'smooth', block: 'start' }) }, 100) } return (
{/* Header */}

API-Referenz

{stats.total} Endpoints in {stats.modules} Modulen

{/* Exposure Stats */}
Exposure: {stats.exposure.public} oeffentlich {stats.exposure.partner} Integration {stats.exposure.internal} intern {stats.exposure.admin} Wartung ({Math.round((stats.exposure.public + stats.exposure.partner) / stats.total * 100)}% exponiert)
{/* Search + Filters */}
setSearch(e.target.value)} className="w-full pl-9 pr-4 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" /> {search && ( )}
{/* Service Filter */}
{([['all', 'Alle'], ['python', 'Python/FastAPI'], ['go', 'Go/Gin']] as const).map(([val, label]) => ( ))}
{/* Exposure Filter */}
{([ ['all', 'Alle'], ['public', 'Oeffentlich'], ['partner', 'Integration'], ['internal', 'Intern'], ['admin', 'Wartung'], ] as const).map(([val, label]) => ( ))}
{/* Method Filter */}
{(['all', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH'] as const).map((m) => ( ))}
{/* Stats Cards */}
{[ { label: 'Endpoints gesamt', value: stats.total, color: 'text-gray-900' }, { label: 'Python / FastAPI', value: stats.python, color: 'text-blue-700' }, { label: 'Go / Gin', value: stats.go, color: 'text-emerald-700' }, { label: 'Module', value: stats.modules, color: 'text-purple-700' }, ].map((s) => (

{s.label}

{s.value}

))}
{/* Module Index (Sidebar) */}

Modul-Index ({filteredModules.length})

{filteredModules.map((m) => ( ))}
{/* Main Content */}
{search && (

{filteredTotal} Treffer in {filteredModules.length} Modulen

)}
{filteredModules.map((m) => { const isExpanded = expandedModules.has(m.id) return (
{ moduleRefs.current[m.id] = el }} className="bg-white rounded-lg border border-gray-200 overflow-hidden" > {/* Module Header */} {/* Endpoints Table */} {isExpanded && (
{m.endpoints.map((e, i) => { const endpointExposure = e.exposure || m.exposure const hasOverride = e.exposure && e.exposure !== m.exposure return ( ) })}
Methode Pfad Beschreibung Exposure
{e.method} {e.path} {e.description} {hasOverride ? ( ) : ( )}
)}
) })}
{filteredModules.length === 0 && (

Keine Endpoints gefunden

Suchbegriff oder Filter anpassen

)}
) }