Files
breakpilot-compliance/admin-compliance/app/sdk/isms/_components/shared.tsx
Sharang Parnerkar ddcd89f26d refactor(admin): split isms page.tsx into colocated components
Split 1260-LOC client page into _types.ts and six tab components under
_components/ (Overview, Policies, SoA, Objectives, Audits, Reviews) plus
a shared helpers module. Behavior preserved exactly; page.tsx is now a
thin wiring shell.

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

87 lines
3.5 KiB
TypeScript

'use client'
import React from 'react'
// =============================================================================
// HELPER COMPONENTS
// =============================================================================
export function StatusBadge({ status, size = 'sm' }: { status: string; size?: 'sm' | 'md' }) {
const colors: Record<string, string> = {
ready: 'bg-green-100 text-green-700',
compliant: 'bg-green-100 text-green-700',
approved: 'bg-green-100 text-green-700',
pass: 'bg-green-100 text-green-700',
implemented: 'bg-green-100 text-green-700',
completed: 'bg-green-100 text-green-700',
verified: 'bg-green-100 text-green-700',
achieved: 'bg-green-100 text-green-700',
closed: 'bg-green-100 text-green-700',
at_risk: 'bg-yellow-100 text-yellow-700',
partial: 'bg-yellow-100 text-yellow-700',
warning: 'bg-yellow-100 text-yellow-700',
planned: 'bg-blue-100 text-blue-700',
draft: 'bg-gray-100 text-gray-700',
active: 'bg-blue-100 text-blue-700',
in_progress: 'bg-blue-100 text-blue-700',
not_ready: 'bg-red-100 text-red-700',
non_compliant: 'bg-red-100 text-red-700',
fail: 'bg-red-100 text-red-700',
open: 'bg-red-100 text-red-700',
corrective_action_pending: 'bg-orange-100 text-orange-700',
verification_pending: 'bg-yellow-100 text-yellow-700',
}
const cls = colors[status] || 'bg-gray-100 text-gray-600'
const labels: Record<string, string> = {
ready: 'Bereit', not_ready: 'Nicht bereit', at_risk: 'Risiko',
compliant: 'Konform', non_compliant: 'Nicht konform', partial: 'Teilweise',
approved: 'Genehmigt', draft: 'Entwurf', pass: 'Bestanden', fail: 'Fehlgeschlagen',
warning: 'Warnung', implemented: 'Implementiert', planned: 'Geplant',
active: 'Aktiv', achieved: 'Erreicht', completed: 'Abgeschlossen',
open: 'Offen', closed: 'Geschlossen', verified: 'Verifiziert',
corrective_action_pending: 'CAPA ausstehend', verification_pending: 'Verifizierung',
in_progress: 'In Bearbeitung',
not_applicable: 'N/A',
}
const pad = size === 'md' ? 'px-3 py-1 text-sm' : 'px-2 py-0.5 text-xs'
return <span className={`${cls} ${pad} rounded-full font-medium`}>{labels[status] || status}</span>
}
export function StatCard({ label, value, sub, color = 'purple' }: { label: string; value: string | number; sub?: string; color?: string }) {
const colors: Record<string, string> = {
purple: 'border-purple-200 bg-purple-50',
green: 'border-green-200 bg-green-50',
red: 'border-red-200 bg-red-50',
yellow: 'border-yellow-200 bg-yellow-50',
blue: 'border-blue-200 bg-blue-50',
}
return (
<div className={`border rounded-xl p-4 ${colors[color] || colors.purple}`}>
<div className="text-2xl font-bold text-gray-900">{value}</div>
<div className="text-sm font-medium text-gray-700 mt-1">{label}</div>
{sub && <div className="text-xs text-gray-500 mt-0.5">{sub}</div>}
</div>
)
}
export function LoadingSpinner() {
return (
<div className="flex items-center justify-center py-20">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-purple-600" />
</div>
)
}
export function EmptyState({ text, action, onAction }: { text: string; action?: string; onAction?: () => void }) {
return (
<div className="text-center py-16 text-gray-500">
<p>{text}</p>
{action && onAction && (
<button onClick={onAction} className="mt-3 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 text-sm">
{action}
</button>
)}
</div>
)
}