Files
breakpilot-compliance/admin-compliance/app/sdk/vvt/_components/FormPrimitives.tsx
Sharang Parnerkar 74927c6f66 refactor(admin): split vvt page.tsx into colocated components
Split the 1371-line VVT page into _components/ extractions
(FormPrimitives, api, TabVerzeichnis, TabEditor, TabExport)
to bring page.tsx under the 300 LOC soft target.

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

97 lines
3.2 KiB
TypeScript

'use client'
import { useState } from 'react'
export function FormSection({ title, children }: { title: string; children: React.ReactNode }) {
return (
<div className="p-4 space-y-3">
<h4 className="font-medium text-gray-800 text-sm">{title}</h4>
{children}
</div>
)
}
export function FormField({ label, children }: { label: string; children: React.ReactNode }) {
return (
<div>
<label className="block text-xs text-gray-500 mb-1">{label}</label>
{children}
</div>
)
}
export function MultiTextInput({ values, onChange, placeholder }: { values: string[]; onChange: (v: string[]) => void; placeholder?: string }) {
const [input, setInput] = useState('')
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && input.trim()) {
e.preventDefault()
onChange([...values, input.trim()])
setInput('')
}
}
return (
<div>
<div className="flex flex-wrap gap-1 mb-2">
{values.map((v, i) => (
<span key={i} className="flex items-center gap-1 px-2 py-1 bg-purple-100 text-purple-700 rounded text-sm">
{v}
<button onClick={() => onChange(values.filter((_, j) => j !== i))} className="text-purple-400 hover:text-purple-600">
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</span>
))}
</div>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleKeyDown}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
placeholder={placeholder}
/>
</div>
)
}
export function CheckboxGrid({ options, selected, onChange }: {
options: { value: string; label: string; highlight?: boolean }[]
selected: string[]
onChange: (v: string[]) => void
}) {
const toggle = (value: string) => {
if (selected.includes(value)) {
onChange(selected.filter(v => v !== value))
} else {
onChange([...selected, value])
}
}
return (
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-1.5">
{options.map(opt => (
<label
key={opt.value}
className={`flex items-center gap-2 p-2 rounded-lg cursor-pointer text-sm transition-colors ${
selected.includes(opt.value)
? opt.highlight ? 'bg-red-50 border border-red-300' : 'bg-purple-50 border border-purple-300'
: opt.highlight ? 'bg-red-50/30 border border-gray-200' : 'border border-gray-200 hover:bg-gray-50'
}`}
>
<input
type="checkbox"
checked={selected.includes(opt.value)}
onChange={() => toggle(opt.value)}
className="w-3.5 h-3.5 text-purple-600 rounded"
/>
<span className={opt.highlight ? 'text-red-700' : 'text-gray-700'}>{opt.label}</span>
{opt.highlight && <span className="text-xs text-red-400">Art.9</span>}
</label>
))}
</div>
)
}