112 lines
3.4 KiB
TypeScript
112 lines
3.4 KiB
TypeScript
import { Plus, Trash2 } from "lucide-react"
|
||
import { Button } from "@/components/ui/button"
|
||
import { Input } from "@/components/ui/input"
|
||
import { Textarea } from "@/components/ui/textarea"
|
||
import {
|
||
Select,
|
||
SelectContent,
|
||
SelectItem,
|
||
SelectTrigger,
|
||
SelectValue,
|
||
} from "@/components/ui/select"
|
||
import type { RecordDTO } from "@/api/types"
|
||
|
||
const RECORD_TYPES = ["A", "AAAA", "CNAME", "MX", "TXT", "SRV", "NS", "SOA"] as const
|
||
|
||
const typeItems = RECORD_TYPES.map((t) => ({ value: t, label: t }))
|
||
|
||
const DEFAULT_TTL = 3600
|
||
|
||
function emptyRecord(): RecordDTO {
|
||
return { type: "A", name: "", ttl: DEFAULT_TTL, values: [""] }
|
||
}
|
||
|
||
export function RecordEditor({
|
||
value,
|
||
onChange,
|
||
}: {
|
||
value: RecordDTO[]
|
||
onChange: (records: RecordDTO[]) => void
|
||
}) {
|
||
function updateRecord(index: number, patch: Partial<RecordDTO>) {
|
||
onChange(value.map((r, i) => (i === index ? { ...r, ...patch } : r)))
|
||
}
|
||
|
||
function addRecord() {
|
||
onChange([...value, emptyRecord()])
|
||
}
|
||
|
||
function removeRecord(index: number) {
|
||
onChange(value.filter((_, i) => i !== index))
|
||
}
|
||
|
||
return (
|
||
<div className="flex flex-col gap-2">
|
||
{value.map((record, index) => (
|
||
<div
|
||
key={index}
|
||
className="flex flex-col gap-2 rounded-lg border border-border bg-background/40 p-2.5 sm:flex-row sm:items-start"
|
||
>
|
||
<Select
|
||
items={typeItems}
|
||
value={record.type}
|
||
onValueChange={(v) => updateRecord(index, { type: v as string })}
|
||
>
|
||
<SelectTrigger aria-label={`Тип записи ${index + 1}`} size="sm" className="font-dns sm:w-24">
|
||
<SelectValue />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
{typeItems.map((item) => (
|
||
<SelectItem key={item.value} value={item.value} className="font-dns">
|
||
{item.label}
|
||
</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
|
||
<Input
|
||
aria-label={`Имя записи ${index + 1}`}
|
||
className="font-dns sm:flex-1"
|
||
placeholder="www"
|
||
value={record.name}
|
||
onChange={(e) => updateRecord(index, { name: e.target.value })}
|
||
/>
|
||
|
||
<Input
|
||
aria-label={`TTL записи ${index + 1}`}
|
||
type="number"
|
||
min={0}
|
||
className="font-dns sm:w-24"
|
||
value={record.ttl}
|
||
onChange={(e) => updateRecord(index, { ttl: Number(e.target.value) })}
|
||
/>
|
||
|
||
<Textarea
|
||
aria-label={`Значения записи ${index + 1}`}
|
||
className="font-dns sm:flex-1"
|
||
placeholder="192.0.2.1"
|
||
rows={1}
|
||
value={record.values.join("\n")}
|
||
onChange={(e) => updateRecord(index, { values: e.target.value.split("\n") })}
|
||
/>
|
||
|
||
<Button
|
||
type="button"
|
||
variant="destructive"
|
||
size="icon-sm"
|
||
aria-label={`Удалить запись ${index + 1}`}
|
||
onClick={() => removeRecord(index)}
|
||
>
|
||
<Trash2 className="size-3.5" strokeWidth={1.75} />
|
||
</Button>
|
||
</div>
|
||
))}
|
||
|
||
<Button type="button" variant="outline" size="sm" onClick={addRecord} className="self-start">
|
||
<Plus className="size-3.5" strokeWidth={1.75} />
|
||
Добавить запись
|
||
</Button>
|
||
</div>
|
||
)
|
||
}
|