Files
breakpilot-compliance/admin-compliance/app/sdk/roadmap/page.tsx
Sharang Parnerkar 6571b580dc refactor(admin): split roadmap page.tsx into colocated components
Split 876-LOC page.tsx into 146 LOC with 7 colocated components
(RoadmapCard, CreateRoadmapModal, CreateItemModal, ImportWizard,
RoadmapDetailView split into header + items table), plus _types.ts,
_constants.ts, and _api.ts. Behavior preserved.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 22:52:20 +02:00

147 lines
6.1 KiB
TypeScript

'use client'
import { useState, useEffect, useCallback } from 'react'
import type { Roadmap } from './_types'
import { statusLabels } from './_constants'
import { api } from './_api'
import { RoadmapCard } from './_components/RoadmapCard'
import { CreateRoadmapModal } from './_components/CreateRoadmapModal'
import { ImportWizard } from './_components/ImportWizard'
import { RoadmapDetailView } from './_components/RoadmapDetailView'
export default function RoadmapPage() {
const [roadmaps, setRoadmaps] = useState<Roadmap[]>([])
const [loading, setLoading] = useState(true)
const [showCreate, setShowCreate] = useState(false)
const [showImport, setShowImport] = useState(false)
const [selectedRoadmap, setSelectedRoadmap] = useState<Roadmap | null>(null)
const [filter, setFilter] = useState<string>('all')
const loadRoadmaps = useCallback(async () => {
setLoading(true)
try {
const data = await api<Roadmap[] | { roadmaps: Roadmap[] }>('')
const list = Array.isArray(data) ? data : (data.roadmaps || [])
setRoadmaps(list)
} catch (err) {
console.error('Load roadmaps error:', err)
} finally {
setLoading(false)
}
}, [])
useEffect(() => { loadRoadmaps() }, [loadRoadmaps])
const handleDelete = async (id: string) => {
if (!confirm('Roadmap wirklich loeschen?')) return
try {
await api(`/${id}`, { method: 'DELETE' })
setRoadmaps(prev => prev.filter(r => r.id !== id))
} catch (err) {
console.error('Delete error:', err)
}
}
const filteredRoadmaps = filter === 'all'
? roadmaps
: roadmaps.filter(r => r.status === filter)
if (selectedRoadmap) {
return (
<div className="p-6 max-w-6xl mx-auto">
<RoadmapDetailView
roadmap={selectedRoadmap}
onBack={() => { setSelectedRoadmap(null); loadRoadmaps() }}
onRefresh={() => {
loadRoadmaps().then(() => {
const updated = roadmaps.find(r => r.id === selectedRoadmap.id)
if (updated) setSelectedRoadmap(updated)
})
}}
/>
</div>
)
}
return (
<div className="p-6 max-w-6xl mx-auto">
<div className="flex items-center justify-between mb-6">
<div>
<h1 className="text-2xl font-bold text-gray-900">Compliance Roadmaps</h1>
<p className="text-sm text-gray-500 mt-1">
Umsetzungsplaene fuer Compliance-Massnahmen
</p>
</div>
<div className="flex gap-2">
<button onClick={() => setShowImport(true)}
className="px-4 py-2 border border-gray-300 text-gray-700 text-sm rounded-lg hover:bg-gray-50 flex items-center gap-2">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
</svg>
Importieren
</button>
<button onClick={() => setShowCreate(true)}
className="px-4 py-2 bg-purple-600 text-white text-sm rounded-lg hover:bg-purple-700 flex items-center gap-2">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
Neue Roadmap
</button>
</div>
</div>
<div className="grid grid-cols-4 gap-4 mb-6">
{[
{ label: 'Gesamt', value: roadmaps.length, color: 'text-gray-900' },
{ label: 'Aktiv', value: roadmaps.filter(r => r.status === 'active').length, color: 'text-green-600' },
{ label: 'Entwurf', value: roadmaps.filter(r => r.status === 'draft').length, color: 'text-gray-600' },
{ label: 'Abgeschlossen', value: roadmaps.filter(r => r.status === 'completed').length, color: 'text-purple-600' },
].map(stat => (
<div key={stat.label} className="bg-white rounded-xl border border-gray-200 p-4 text-center">
<div className={`text-2xl font-bold ${stat.color}`}>{stat.value}</div>
<div className="text-xs text-gray-500">{stat.label}</div>
</div>
))}
</div>
<div className="flex gap-2 mb-6">
{['all', 'draft', 'active', 'completed'].map(f => (
<button key={f} onClick={() => setFilter(f)}
className={`px-3 py-1.5 text-sm rounded-lg ${filter === f ? 'bg-purple-600 text-white' : 'bg-gray-100 text-gray-600 hover:bg-gray-200'}`}>
{f === 'all' ? 'Alle' : statusLabels[f] || f}
</button>
))}
</div>
{loading ? (
<div className="text-center py-12 text-gray-500">Roadmaps werden geladen...</div>
) : filteredRoadmaps.length === 0 ? (
<div className="text-center py-12">
<div className="text-gray-400 mb-2">
<svg className="w-12 h-12 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2" />
</svg>
</div>
<p className="text-gray-500">Keine Roadmaps gefunden</p>
<button onClick={() => setShowCreate(true)} className="mt-3 text-sm text-purple-600 hover:text-purple-700">
Erste Roadmap erstellen
</button>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{filteredRoadmaps.map(r => (
<RoadmapCard key={r.id} roadmap={r} onSelect={setSelectedRoadmap} onDelete={handleDelete} />
))}
</div>
)}
{showCreate && (
<CreateRoadmapModal onClose={() => setShowCreate(false)} onCreated={() => { setShowCreate(false); loadRoadmaps() }} />
)}
{showImport && (
<ImportWizard onClose={() => setShowImport(false)} onImported={() => { setShowImport(false); loadRoadmaps() }} />
)}
</div>
)
}