Files
breakpilot-compliance/admin-compliance/app/sdk/evidence/_components/EvidenceCard.tsx
Sharang Parnerkar 7907b3f25b refactor(admin): split evidence, import, portfolio pages
Extract components and hooks from oversized pages into colocated
_components/ and _hooks/ subdirectories to enforce the 500-LOC hard cap.
page.tsx files reduced to 205, 121, and 136 LOC respectively.

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

135 lines
5.7 KiB
TypeScript

'use client'
import type { DisplayEvidence, DisplayEvidenceType } from './EvidenceTypes'
const typeIcons: Record<DisplayEvidenceType, React.ReactNode> = {
document: (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
),
screenshot: (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
),
log: (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
</svg>
),
'audit-report': (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
),
certificate: (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z" />
</svg>
),
}
const statusColors = {
valid: 'bg-green-100 text-green-700 border-green-200',
expired: 'bg-red-100 text-red-700 border-red-200',
'pending-review': 'bg-yellow-100 text-yellow-700 border-yellow-200',
}
const statusLabels = {
valid: 'Gueltig',
expired: 'Abgelaufen',
'pending-review': 'Pruefung ausstehend',
}
const typeIconBg: Record<DisplayEvidenceType, string> = {
certificate: 'bg-yellow-100 text-yellow-600',
'audit-report': 'bg-purple-100 text-purple-600',
screenshot: 'bg-blue-100 text-blue-600',
log: 'bg-green-100 text-green-600',
document: 'bg-gray-100 text-gray-600',
}
export function EvidenceCard({
evidence,
onDelete,
onView,
onDownload,
}: {
evidence: DisplayEvidence
onDelete: () => void
onView: () => void
onDownload: () => void
}) {
return (
<div className={`bg-white rounded-xl border-2 p-6 ${
evidence.status === 'expired' ? 'border-red-200' :
evidence.status === 'pending-review' ? 'border-yellow-200' : 'border-gray-200'
}`}>
<div className="flex items-start gap-4">
<div className={`w-12 h-12 rounded-lg flex items-center justify-center ${typeIconBg[evidence.displayType]}`}>
{typeIcons[evidence.displayType]}
</div>
<div className="flex-1">
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold text-gray-900">{evidence.name}</h3>
<span className={`px-3 py-1 text-xs rounded-full ${statusColors[evidence.status]}`}>
{statusLabels[evidence.status]}
</span>
</div>
<p className="text-sm text-gray-500 mt-1">{evidence.description}</p>
<div className="mt-3 flex items-center gap-4 text-sm text-gray-500">
<span>Hochgeladen: {evidence.uploadedAt.toLocaleDateString('de-DE')}</span>
{evidence.validUntil && (
<span className={evidence.status === 'expired' ? 'text-red-600' : ''}>
Gueltig bis: {evidence.validUntil.toLocaleDateString('de-DE')}
</span>
)}
<span>{evidence.fileSize}</span>
</div>
<div className="mt-3 flex items-center gap-2 flex-wrap">
{evidence.linkedRequirements.map(req => (
<span key={req} className="px-2 py-0.5 text-xs bg-blue-50 text-blue-600 rounded">
{req}
</span>
))}
{evidence.linkedControls.map(ctrl => (
<span key={ctrl} className="px-2 py-0.5 text-xs bg-green-50 text-green-600 rounded">
{ctrl}
</span>
))}
</div>
</div>
</div>
<div className="mt-4 pt-4 border-t border-gray-100 flex items-center justify-between">
<span className="text-sm text-gray-500">Hochgeladen von: {evidence.uploadedBy}</span>
<div className="flex items-center gap-2">
<button
onClick={onView}
disabled={!evidence.fileUrl}
className="px-3 py-1 text-sm text-gray-600 hover:bg-gray-100 rounded-lg transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
>
Anzeigen
</button>
<button
onClick={onDownload}
disabled={!evidence.fileUrl}
className="px-3 py-1 text-sm text-purple-600 hover:bg-purple-50 rounded-lg transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
>
Herunterladen
</button>
<button
onClick={onDelete}
className="px-3 py-1 text-sm text-red-600 hover:bg-red-50 rounded-lg transition-colors"
>
Loeschen
</button>
</div>
</div>
</div>
)
}