fix(admin-v2): Add missing utils and DSFA components for build
- Add cn() utility function for className merging - Add DSFACard, RiskMatrix, ApprovalPanel components Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
184
admin-v2/components/sdk/dsfa/index.ts
Normal file
184
admin-v2/components/sdk/dsfa/index.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
// =============================================================================
|
||||
// DSFA Card Component
|
||||
// =============================================================================
|
||||
|
||||
interface DSFACardProps {
|
||||
dsfa: {
|
||||
id: string
|
||||
title: string
|
||||
status: string
|
||||
risk_level?: string
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
processing_description?: string
|
||||
}
|
||||
onDelete?: (id: string) => void
|
||||
onExport?: (id: string) => void
|
||||
}
|
||||
|
||||
export function DSFACard({ dsfa, onDelete, onExport }: DSFACardProps) {
|
||||
const statusColors: Record<string, string> = {
|
||||
draft: 'bg-slate-100 text-slate-700',
|
||||
in_progress: 'bg-blue-100 text-blue-700',
|
||||
review: 'bg-amber-100 text-amber-700',
|
||||
approved: 'bg-green-100 text-green-700',
|
||||
rejected: 'bg-red-100 text-red-700',
|
||||
}
|
||||
|
||||
return React.createElement('div', {
|
||||
className: 'bg-white rounded-xl border border-slate-200 p-5 hover:shadow-md transition-shadow'
|
||||
},
|
||||
React.createElement('div', { className: 'flex items-start justify-between mb-3' },
|
||||
React.createElement('h3', { className: 'font-semibold text-slate-900 text-lg' }, dsfa.title),
|
||||
React.createElement('span', {
|
||||
className: `px-2.5 py-1 rounded-full text-xs font-medium ${statusColors[dsfa.status] || statusColors.draft}`
|
||||
}, dsfa.status)
|
||||
),
|
||||
dsfa.processing_description && React.createElement('p', {
|
||||
className: 'text-sm text-slate-500 mb-4 line-clamp-2'
|
||||
}, dsfa.processing_description),
|
||||
React.createElement('div', { className: 'flex items-center gap-2' },
|
||||
React.createElement('a', {
|
||||
href: `/sdk/dsfa/${dsfa.id}`,
|
||||
className: 'px-3 py-1.5 bg-primary-600 text-white rounded-lg text-sm hover:bg-primary-700 transition-colors'
|
||||
}, 'Bearbeiten'),
|
||||
onExport && React.createElement('button', {
|
||||
onClick: () => onExport(dsfa.id),
|
||||
className: 'px-3 py-1.5 bg-slate-100 text-slate-700 rounded-lg text-sm hover:bg-slate-200 transition-colors'
|
||||
}, 'Export'),
|
||||
onDelete && React.createElement('button', {
|
||||
onClick: () => onDelete(dsfa.id),
|
||||
className: 'px-3 py-1.5 text-red-600 rounded-lg text-sm hover:bg-red-50 transition-colors'
|
||||
}, 'Loeschen')
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Risk Matrix Component
|
||||
// =============================================================================
|
||||
|
||||
interface RiskMatrixProps {
|
||||
risks: Array<{
|
||||
id: string
|
||||
title: string
|
||||
probability: number
|
||||
impact: number
|
||||
risk_level?: string
|
||||
}>
|
||||
onRiskClick?: (riskId: string) => void
|
||||
}
|
||||
|
||||
export function RiskMatrix({ risks, onRiskClick }: RiskMatrixProps) {
|
||||
const levels = [1, 2, 3, 4, 5]
|
||||
const levelLabels = ['Sehr gering', 'Gering', 'Mittel', 'Hoch', 'Sehr hoch']
|
||||
const cellColors: Record<string, string> = {
|
||||
low: 'bg-green-100 hover:bg-green-200',
|
||||
medium: 'bg-yellow-100 hover:bg-yellow-200',
|
||||
high: 'bg-orange-100 hover:bg-orange-200',
|
||||
critical: 'bg-red-100 hover:bg-red-200',
|
||||
}
|
||||
|
||||
const getRiskColor = (prob: number, impact: number) => {
|
||||
const score = prob * impact
|
||||
if (score <= 4) return cellColors.low
|
||||
if (score <= 9) return cellColors.medium
|
||||
if (score <= 16) return cellColors.high
|
||||
return cellColors.critical
|
||||
}
|
||||
|
||||
return React.createElement('div', { className: 'bg-white rounded-xl border border-slate-200 p-5' },
|
||||
React.createElement('h3', { className: 'font-semibold text-slate-900 mb-4' }, 'Risikomatrix'),
|
||||
React.createElement('div', { className: 'grid grid-cols-6 gap-1' },
|
||||
React.createElement('div'),
|
||||
...levels.map(l => React.createElement('div', {
|
||||
key: `h-${l}`,
|
||||
className: 'text-center text-xs text-slate-500 py-1'
|
||||
}, levelLabels[l - 1])),
|
||||
...levels.reverse().map(prob =>
|
||||
[
|
||||
React.createElement('div', {
|
||||
key: `l-${prob}`,
|
||||
className: 'text-right text-xs text-slate-500 pr-2 flex items-center justify-end'
|
||||
}, levelLabels[prob - 1]),
|
||||
...levels.map(impact => {
|
||||
const cellRisks = risks.filter(r => r.probability === prob && r.impact === impact)
|
||||
return React.createElement('div', {
|
||||
key: `${prob}-${impact}`,
|
||||
className: `aspect-square rounded ${getRiskColor(prob, impact)} flex items-center justify-center text-xs font-medium cursor-pointer`,
|
||||
onClick: () => cellRisks[0] && onRiskClick?.(cellRisks[0].id)
|
||||
}, cellRisks.length > 0 ? String(cellRisks.length) : '')
|
||||
})
|
||||
]
|
||||
).flat()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Approval Panel Component
|
||||
// =============================================================================
|
||||
|
||||
interface ApprovalPanelProps {
|
||||
dsfa: {
|
||||
id: string
|
||||
status: string
|
||||
approved_by?: string
|
||||
approved_at?: string
|
||||
rejection_reason?: string
|
||||
}
|
||||
onApprove?: () => void
|
||||
onReject?: (reason: string) => void
|
||||
}
|
||||
|
||||
export function ApprovalPanel({ dsfa, onApprove, onReject }: ApprovalPanelProps) {
|
||||
const [rejectionReason, setRejectionReason] = React.useState('')
|
||||
const [showRejectForm, setShowRejectForm] = React.useState(false)
|
||||
|
||||
if (dsfa.status === 'approved') {
|
||||
return React.createElement('div', {
|
||||
className: 'bg-green-50 border border-green-200 rounded-xl p-5'
|
||||
},
|
||||
React.createElement('div', { className: 'flex items-center gap-2 mb-2' },
|
||||
React.createElement('span', { className: 'text-green-600 text-lg' }, '\u2713'),
|
||||
React.createElement('h3', { className: 'font-semibold text-green-800' }, 'DSFA genehmigt')
|
||||
),
|
||||
dsfa.approved_by && React.createElement('p', { className: 'text-sm text-green-700' },
|
||||
`Genehmigt von: ${dsfa.approved_by}`
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return React.createElement('div', {
|
||||
className: 'bg-white rounded-xl border border-slate-200 p-5'
|
||||
},
|
||||
React.createElement('h3', { className: 'font-semibold text-slate-900 mb-4' }, 'Freigabe'),
|
||||
React.createElement('div', { className: 'flex gap-3' },
|
||||
onApprove && React.createElement('button', {
|
||||
onClick: onApprove,
|
||||
className: 'px-4 py-2 bg-green-600 text-white rounded-lg text-sm hover:bg-green-700 transition-colors'
|
||||
}, 'Genehmigen'),
|
||||
onReject && React.createElement('button', {
|
||||
onClick: () => setShowRejectForm(true),
|
||||
className: 'px-4 py-2 bg-red-600 text-white rounded-lg text-sm hover:bg-red-700 transition-colors'
|
||||
}, 'Ablehnen')
|
||||
),
|
||||
showRejectForm && React.createElement('div', { className: 'mt-4' },
|
||||
React.createElement('textarea', {
|
||||
value: rejectionReason,
|
||||
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => setRejectionReason(e.target.value),
|
||||
placeholder: 'Ablehnungsgrund...',
|
||||
className: 'w-full p-3 border border-slate-200 rounded-lg text-sm resize-none',
|
||||
rows: 3
|
||||
}),
|
||||
React.createElement('button', {
|
||||
onClick: () => { onReject?.(rejectionReason); setShowRejectForm(false) },
|
||||
className: 'mt-2 px-4 py-2 bg-red-600 text-white rounded-lg text-sm hover:bg-red-700'
|
||||
}, 'Ablehnung senden')
|
||||
)
|
||||
)
|
||||
}
|
||||
7
admin-v2/lib/utils.ts
Normal file
7
admin-v2/lib/utils.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Utility function for merging class names.
|
||||
* Filters out falsy values and joins with spaces.
|
||||
*/
|
||||
export function cn(...classes: (string | undefined | null | false)[]): string {
|
||||
return classes.filter(Boolean).join(' ')
|
||||
}
|
||||
Reference in New Issue
Block a user