Files
breakpilot-compliance/admin-compliance/app/sdk/cra/[projectId]/backlog/page.tsx
T
Benjamin Admin b19d76407d chore(cra): align CRA module to the dev/demo tenant + demo-customer seed script
CRA frontend pages hardcoded tenant 00000000-…-001 while IACE uses the dev
tenant 9282a473-… → a demo customer was split/invisible across modules. Align all
app/sdk/cra pages to 9282a473-… so the whole CRA<->IACE journey lives under ONE
tenant. Add scripts/seed_demo_customer.py: seeds CompanyProfile + IACE project
(components, hazards, mitigations) + CRA project (intake, scope-check, assessment
snapshot from faked repo findings + components + safety functions) — the source-
repo layer is faked so the full frontend is walkable once.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-14 15:52:49 +02:00

156 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import React, { useEffect, useState, useCallback, use } from 'react'
import { SeverityBadge } from '../../_components/SeverityBadge'
interface BacklogItem {
rank: number
req_id: string
title: string
category: string
severity: string
annex_anchor: string
description: string
effort_days: number
mapped_measure_names: { id: string; name: string }[]
status: string
priority_score: number
}
interface BacklogResponse {
project_id: string
classification: string | null
days_to_ce_deadline: number
deadlines: { date: string; label: string }[]
total: number
items: BacklogItem[]
}
export default function BacklogPage({
params,
}: {
params: Promise<{ projectId: string }>
}) {
const { projectId } = use(params)
const [data, setData] = useState<BacklogResponse | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState('')
const load = useCallback(async () => {
try {
const res = await fetch(`/api/sdk/v1/cra/projects/${projectId}/backlog`, {
headers: { 'X-Tenant-ID': '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' },
})
if (!res.ok) throw new Error(await res.text())
setData(await res.json())
} catch (e) {
setError(e instanceof Error ? e.message : 'Fehler beim Laden')
} finally {
setLoading(false)
}
}, [projectId])
useEffect(() => { load() }, [load])
if (loading) return <div className="min-h-screen bg-gray-50 p-8"><p className="text-gray-500">Laedt...</p></div>
if (error) return <div className="min-h-screen bg-gray-50 p-8"><p className="text-red-600">{error}</p></div>
if (!data) return null
return (
<div className="min-h-screen bg-gray-50 py-8">
<div className="max-w-6xl mx-auto px-4">
<div className="mb-6">
<a href={`/sdk/cra/${projectId}`} className="text-sm text-blue-600 hover:underline">
&larr; Zurueck zum Projekt
</a>
<h1 className="text-2xl font-bold text-gray-900 mt-2">Prioritaeten-Backlog</h1>
<p className="text-sm text-gray-600 mt-1">
Sortiert nach Severity × Deadline-Druck × Effort. Was du heute tust, was naechsten Sprint, was vor 11.12.2027.
</p>
</div>
{/* Deadline-Banner */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 mb-6">
{data.deadlines.map(d => {
const days = Math.max(0, Math.round((new Date(d.date).getTime() - Date.now()) / 86400000))
const isPast = new Date(d.date).getTime() < Date.now()
return (
<div
key={d.date}
className={`rounded-xl border p-4 ${
isPast ? 'bg-gray-100 border-gray-200' :
days < 90 ? 'bg-red-50 border-red-200' :
days < 365 ? 'bg-orange-50 border-orange-200' :
'bg-blue-50 border-blue-200'
}`}
>
<div className="text-xs text-gray-500">{d.date}</div>
<div className="font-semibold text-gray-900 text-sm mt-0.5">{d.label}</div>
<div className={`text-xs mt-1 ${isPast ? 'text-gray-500' : 'text-gray-700'}`}>
{isPast ? 'bereits abgelaufen' : `noch ${days} Tage`}
</div>
</div>
)
})}
</div>
{/* Backlog */}
<div className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
<table className="w-full">
<thead className="bg-gray-50 border-b border-gray-200">
<tr>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Rang</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Anforderung</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Severity</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Score</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Aufwand</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Massnahme</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Aktion</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-100">
{data.items.map(item => (
<tr key={item.req_id} className="hover:bg-gray-50">
<td className="px-3 py-3 text-sm font-bold text-gray-700">{item.rank}</td>
<td className="px-3 py-3">
<div className="text-sm font-medium text-gray-900">{item.title}</div>
<div className="text-xs text-gray-500">{item.category} · {item.annex_anchor}</div>
</td>
<td className="px-3 py-3"><SeverityBadge value={item.severity} /></td>
<td className="px-3 py-3 text-sm font-mono text-gray-700">{item.priority_score}</td>
<td className="px-3 py-3 text-sm text-gray-600">{item.effort_days} PT</td>
<td className="px-3 py-3 text-xs text-gray-600">
{item.mapped_measure_names.length > 0 ? (
<div className="space-y-0.5">
{item.mapped_measure_names.map(m => (
<div key={m.id} title={m.name}>
<span className="font-mono text-gray-400">{m.id}:</span> {m.name.length > 50 ? m.name.slice(0, 50) + '...' : m.name}
</div>
))}
</div>
) : (
<span className="text-gray-400"></span>
)}
</td>
<td className="px-3 py-3">
<button
className="px-2 py-1 text-xs bg-purple-100 text-purple-800 rounded hover:bg-purple-200"
onClick={() => alert(`Jira-Export fuer ${item.req_id} — Phase-4-Feature`)}
>
Jira
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
<p className="text-xs text-gray-500 mt-4 text-center">
Tage bis CE-Marking-Pflicht (11.12.2027): <span className="font-semibold">{data.days_to_ce_deadline}</span>
</p>
</div>
</div>
)
}