Files
breakpilot-compliance/admin-compliance/app/sdk/compliance-hub/_components/TrendTab.tsx
Sharang Parnerkar 2ade65431a refactor(admin): split compliance-hub, obligations, document-generator pages
Each page.tsx was >1000 LOC; extract components to _components/ and hooks
to _hooks/ so page files stay under 500 LOC (164 / 255 / 243 respectively).
Zero behavior changes — logic relocated verbatim.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 17:10:14 +02:00

105 lines
4.5 KiB
TypeScript

'use client'
import type { ScoreSnapshot } from './types'
interface TrendTabProps {
scoreHistory: ScoreSnapshot[]
savingSnapshot: boolean
saveSnapshot: () => Promise<void>
}
export function TrendTab({ scoreHistory, savingSnapshot, saveSnapshot }: TrendTabProps) {
return (
<div className="bg-white rounded-xl shadow-sm border p-6">
<div className="flex items-center justify-between mb-6">
<h3 className="text-lg font-semibold text-slate-900">Score-Verlauf</h3>
<button
onClick={saveSnapshot}
disabled={savingSnapshot}
className="px-3 py-1.5 text-sm bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50"
>
{savingSnapshot ? 'Speichere...' : 'Aktuellen Score speichern'}
</button>
</div>
{scoreHistory.length === 0 ? (
<div className="text-center py-12">
<p className="text-slate-500">Noch keine Score-Snapshots vorhanden.</p>
<p className="text-sm text-slate-400 mt-1">Klicken Sie auf "Aktuellen Score speichern", um den ersten Datenpunkt zu erstellen.</p>
</div>
) : (
<>
{/* Simple SVG Line Chart */}
<div className="relative h-64 mb-6">
<svg className="w-full h-full" viewBox="0 0 800 200" preserveAspectRatio="none">
{/* Grid lines */}
{[0, 25, 50, 75, 100].map(pct => (
<line key={pct} x1="0" y1={200 - pct * 2} x2="800" y2={200 - pct * 2}
stroke="#e2e8f0" strokeWidth="1" />
))}
{/* Score line */}
<polyline
fill="none"
stroke="#9333ea"
strokeWidth="3"
strokeLinejoin="round"
points={scoreHistory.map((s, i) => {
const x = scoreHistory.length === 1 ? 400 : (i / (scoreHistory.length - 1)) * 780 + 10
const y = 200 - (s.score / 100) * 200
return `${x},${y}`
}).join(' ')}
/>
{/* Points */}
{scoreHistory.map((s, i) => {
const x = scoreHistory.length === 1 ? 400 : (i / (scoreHistory.length - 1)) * 780 + 10
const y = 200 - (s.score / 100) * 200
return (
<circle key={i} cx={x} cy={y} r="5" fill="#9333ea" stroke="white" strokeWidth="2" />
)
})}
</svg>
{/* Y-axis labels */}
<div className="absolute left-0 top-0 h-full flex flex-col justify-between text-xs text-slate-400 -ml-2">
<span>100%</span>
<span>75%</span>
<span>50%</span>
<span>25%</span>
<span>0%</span>
</div>
</div>
{/* Snapshot Table */}
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead className="bg-slate-50">
<tr>
<th className="px-4 py-2 text-left text-xs font-medium text-slate-500 uppercase">Datum</th>
<th className="px-4 py-2 text-center text-xs font-medium text-slate-500 uppercase">Score</th>
<th className="px-4 py-2 text-center text-xs font-medium text-slate-500 uppercase">Controls</th>
<th className="px-4 py-2 text-center text-xs font-medium text-slate-500 uppercase">Bestanden</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-200">
{scoreHistory.slice().reverse().map(snap => (
<tr key={snap.id} className="hover:bg-slate-50">
<td className="px-4 py-2 text-slate-700">{new Date(snap.snapshot_date).toLocaleDateString('de-DE')}</td>
<td className="px-4 py-2 text-center">
<span className={`font-bold ${
snap.score >= 80 ? 'text-green-600' : snap.score >= 60 ? 'text-yellow-600' : 'text-red-600'
}`}>
{typeof snap.score === 'number' ? snap.score.toFixed(1) : snap.score}%
</span>
</td>
<td className="px-4 py-2 text-center text-slate-600">{snap.controls_total}</td>
<td className="px-4 py-2 text-center text-slate-600">{snap.controls_pass}</td>
</tr>
))}
</tbody>
</table>
</div>
</>
)}
</div>
)
}