Files
breakpilot-compliance/admin-compliance/app/sdk/roadmap/_components/RoadmapDetailView.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

125 lines
4.5 KiB
TypeScript

import { useState, useEffect, useCallback } from 'react'
import type { Roadmap, RoadmapItem, RoadmapStats } from '../_types'
import { itemStatusLabels } from '../_constants'
import { api } from '../_api'
import { RoadmapDetailHeader } from './RoadmapDetailHeader'
import { RoadmapItemsTable } from './RoadmapItemsTable'
import { CreateItemModal } from './CreateItemModal'
export function RoadmapDetailView({ roadmap, onBack, onRefresh }: {
roadmap: Roadmap
onBack: () => void
onRefresh: () => void
}) {
const [items, setItems] = useState<RoadmapItem[]>([])
const [stats, setStats] = useState<RoadmapStats | null>(null)
const [loading, setLoading] = useState(true)
const [showCreateItem, setShowCreateItem] = useState(false)
const [filterStatus, setFilterStatus] = useState<string>('all')
const [filterPriority, setFilterPriority] = useState<string>('all')
const loadDetails = useCallback(async () => {
setLoading(true)
try {
const [i, s] = await Promise.all([
api<RoadmapItem[] | { items: RoadmapItem[] }>(`/${roadmap.id}/items`).catch(() => []),
api<RoadmapStats>(`/${roadmap.id}/stats`).catch(() => null),
])
const itemList = Array.isArray(i) ? i : ((i as { items: RoadmapItem[] }).items || [])
setItems(itemList)
setStats(s)
} finally {
setLoading(false)
}
}, [roadmap.id])
useEffect(() => { loadDetails() }, [loadDetails])
const handleStatusChange = async (itemId: string, newStatus: string) => {
try {
const res = await fetch(`/api/sdk/v1/roadmap-items/${itemId}/status`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: newStatus }),
})
if (!res.ok) throw new Error(`HTTP ${res.status}`)
loadDetails()
} catch (err) {
console.error('Status change error:', err)
}
}
const handleDeleteItem = async (itemId: string) => {
try {
const res = await fetch(`/api/sdk/v1/roadmap-items/${itemId}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
})
if (!res.ok) throw new Error(`HTTP ${res.status}`)
setItems(prev => prev.filter(i => i.id !== itemId))
} catch (err) {
console.error('Delete item error:', err)
}
}
const filteredItems = items.filter(i => {
if (filterStatus !== 'all' && i.status !== filterStatus) return false
if (filterPriority !== 'all' && i.priority !== filterPriority) return false
return true
})
return (
<div>
<button onClick={onBack} className="flex items-center gap-2 text-sm text-gray-600 hover:text-gray-900 mb-4">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
Zurueck zur Uebersicht
</button>
<RoadmapDetailHeader
roadmap={roadmap}
stats={stats}
onCreateItem={() => setShowCreateItem(true)}
/>
<div className="flex gap-4 mb-4">
<div className="flex items-center gap-2">
<span className="text-sm text-gray-500">Status:</span>
<select value={filterStatus} onChange={e => setFilterStatus(e.target.value)}
className="px-2 py-1 text-sm border rounded-lg">
<option value="all">Alle</option>
{Object.entries(itemStatusLabels).map(([k, v]) => <option key={k} value={k}>{v}</option>)}
</select>
</div>
<div className="flex items-center gap-2">
<span className="text-sm text-gray-500">Prioritaet:</span>
<select value={filterPriority} onChange={e => setFilterPriority(e.target.value)}
className="px-2 py-1 text-sm border rounded-lg">
<option value="all">Alle</option>
<option value="CRITICAL">Kritisch</option>
<option value="HIGH">Hoch</option>
<option value="MEDIUM">Mittel</option>
<option value="LOW">Niedrig</option>
</select>
</div>
</div>
{loading ? (
<div className="text-center py-8 text-gray-500">Laden...</div>
) : (
<RoadmapItemsTable
items={filteredItems}
onStatusChange={handleStatusChange}
onDelete={handleDeleteItem}
/>
)}
{showCreateItem && (
<CreateItemModal roadmapId={roadmap.id} onClose={() => setShowCreateItem(false)}
onCreated={() => { setShowCreateItem(false); loadDetails(); onRefresh() }} />
)}
</div>
)
}