Each page.tsx exceeded the 500-LOC hard cap. Extracted components and hooks into colocated _components/ and _hooks/ directories; page.tsx is now a thin orchestrator. - controls/page.tsx: 944 → 180 LOC; extracted ControlCard, AddControlForm, LoadingSkeleton, TransitionErrorBanner, StatsCards, FilterBar, RAGPanel into _components/ and useControlsData, useRAGSuggestions into _hooks/; types into _types.ts - training/page.tsx: 780 → 288 LOC; extracted ContentTab (inline content generator tab) into _components/ContentTab.tsx - control-provenance/page.tsx: 739 → 122 LOC; extracted MarkdownRenderer, UsageBadge, PermBadge, LicenseMatrix, SourceRegistry into _components/; PROVENANCE_SECTIONS static data into _data/provenance-sections.ts - iace/[projectId]/verification/page.tsx: 673 → 196 LOC; extracted StatusBadge, VerificationForm, CompleteModal, SuggestEvidenceModal, VerificationTable into _components/ Zero behavior changes; logic relocated verbatim. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
105 lines
3.9 KiB
TypeScript
105 lines
3.9 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import type { ControlType } from '@/lib/sdk'
|
|
|
|
interface FormData {
|
|
name: string
|
|
description: string
|
|
type: ControlType
|
|
category: string
|
|
owner: string
|
|
}
|
|
|
|
export function AddControlForm({
|
|
onSubmit,
|
|
onCancel,
|
|
}: {
|
|
onSubmit: (data: FormData) => void
|
|
onCancel: () => void
|
|
}) {
|
|
const [formData, setFormData] = useState<FormData>({
|
|
name: '',
|
|
description: '',
|
|
type: 'TECHNICAL',
|
|
category: '',
|
|
owner: '',
|
|
})
|
|
|
|
return (
|
|
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
|
<h3 className="text-lg font-semibold text-gray-900 mb-4">Neue Kontrolle</h3>
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Name *</label>
|
|
<input
|
|
type="text"
|
|
value={formData.name}
|
|
onChange={e => setFormData({ ...formData, name: e.target.value })}
|
|
placeholder="z.B. Zugriffskontrolle"
|
|
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Beschreibung</label>
|
|
<textarea
|
|
value={formData.description}
|
|
onChange={e => setFormData({ ...formData, description: e.target.value })}
|
|
placeholder="Beschreiben Sie die Kontrolle..."
|
|
rows={2}
|
|
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-3 gap-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Typ</label>
|
|
<select
|
|
value={formData.type}
|
|
onChange={e => setFormData({ ...formData, type: e.target.value as ControlType })}
|
|
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
|
>
|
|
<option value="TECHNICAL">Technisch</option>
|
|
<option value="ORGANIZATIONAL">Organisatorisch</option>
|
|
<option value="PHYSICAL">Physisch</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Kategorie</label>
|
|
<input
|
|
type="text"
|
|
value={formData.category}
|
|
onChange={e => setFormData({ ...formData, category: e.target.value })}
|
|
placeholder="z.B. Zutrittskontrolle"
|
|
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Verantwortlich</label>
|
|
<input
|
|
type="text"
|
|
value={formData.owner}
|
|
onChange={e => setFormData({ ...formData, owner: e.target.value })}
|
|
placeholder="z.B. IT Security"
|
|
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="mt-6 flex items-center justify-end gap-3">
|
|
<button onClick={onCancel} className="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors">
|
|
Abbrechen
|
|
</button>
|
|
<button
|
|
onClick={() => onSubmit(formData)}
|
|
disabled={!formData.name}
|
|
className={`px-6 py-2 rounded-lg font-medium transition-colors ${
|
|
formData.name ? 'bg-purple-600 text-white hover:bg-purple-700' : 'bg-gray-200 text-gray-400 cursor-not-allowed'
|
|
}`}
|
|
>
|
|
Hinzufuegen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|