Extract components and hooks into _components/ and _hooks/ subdirectories to reduce each page.tsx to under 500 LOC (was 1545/1383/1316). Final line counts: evidence=213, process-tasks=304, hazards=157. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
77 lines
3.3 KiB
TypeScript
77 lines
3.3 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import type { DisplayEvidence } from './EvidenceTypes'
|
|
|
|
export function RejectModal({ evidence, onClose, onSuccess }: {
|
|
evidence: DisplayEvidence
|
|
onClose: () => void
|
|
onSuccess: () => void
|
|
}) {
|
|
const [reviewedBy, setReviewedBy] = useState('')
|
|
const [rejectionReason, setRejectionReason] = useState('')
|
|
const [submitting, setSubmitting] = useState(false)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
const handleSubmit = async () => {
|
|
if (!reviewedBy.trim()) { setError('Bitte E-Mail-Adresse angeben'); return }
|
|
if (!rejectionReason.trim()) { setError('Bitte Ablehnungsgrund angeben'); return }
|
|
setSubmitting(true)
|
|
setError(null)
|
|
try {
|
|
const res = await fetch(`/api/sdk/v1/compliance/evidence/${evidence.id}/reject`, {
|
|
method: 'PATCH',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ reviewed_by: reviewedBy, rejection_reason: rejectionReason }),
|
|
})
|
|
if (!res.ok) {
|
|
const err = await res.json().catch(() => ({ detail: 'Ablehnung fehlgeschlagen' }))
|
|
throw new Error(typeof err.detail === 'string' ? err.detail : JSON.stringify(err.detail))
|
|
}
|
|
onSuccess()
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Unbekannter Fehler')
|
|
} finally {
|
|
setSubmitting(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50" onClick={onClose}>
|
|
<div className="bg-white rounded-2xl shadow-xl w-full max-w-lg mx-4 p-6" onClick={e => e.stopPropagation()}>
|
|
<h2 className="text-xl font-bold text-gray-900 mb-4">Evidence Ablehnen</h2>
|
|
<p className="text-sm text-gray-500 mb-4">{evidence.name}</p>
|
|
|
|
<div className="mb-4">
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Reviewer (E-Mail)</label>
|
|
<input type="email" value={reviewedBy} onChange={e => setReviewedBy(e.target.value)}
|
|
placeholder="reviewer@unternehmen.de"
|
|
className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-red-500 focus:border-transparent" />
|
|
</div>
|
|
|
|
<div className="mb-4">
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Ablehnungsgrund</label>
|
|
<textarea value={rejectionReason} onChange={e => setRejectionReason(e.target.value)}
|
|
placeholder="Bitte beschreiben Sie den Grund fuer die Ablehnung..."
|
|
rows={4}
|
|
className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-red-500 focus:border-transparent resize-none" />
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="mb-4 p-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">{error}</div>
|
|
)}
|
|
|
|
<div className="flex justify-end gap-3">
|
|
<button onClick={onClose} className="px-4 py-2 text-sm text-gray-600 hover:bg-gray-100 rounded-lg transition-colors">
|
|
Abbrechen
|
|
</button>
|
|
<button onClick={handleSubmit} disabled={submitting}
|
|
className="px-4 py-2 text-sm bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors disabled:opacity-50">
|
|
{submitting ? 'Wird abgelehnt...' : 'Ablehnen'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|