w-funnel/src/components/admin/builder/templates/FormScreenConfig.tsx
dev.daminik00 0fc1dc756e admin
2025-09-27 05:48:42 +02:00

238 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { Button } from "@/components/ui/button";
import { TextInput } from "@/components/ui/TextInput/TextInput";
import { Plus } from "lucide-react";
import type { FormScreenDefinition, FormFieldDefinition, FormValidationMessages } from "@/lib/funnel/types";
import type { BuilderScreen } from "@/lib/admin/builder/types";
interface FormScreenConfigProps {
screen: BuilderScreen & { template: "form" };
onUpdate: (updates: Partial<FormScreenDefinition>) => void;
}
export function FormScreenConfig({ screen, onUpdate }: FormScreenConfigProps) {
const formScreen = screen as FormScreenDefinition & { position: { x: number; y: number } };
const updateField = (index: number, updates: Partial<FormFieldDefinition>) => {
const newFields = [...(formScreen.fields || [])];
newFields[index] = { ...newFields[index], ...updates };
onUpdate({ fields: newFields });
};
const updateValidationMessages = (updates: Partial<FormValidationMessages>) => {
onUpdate({
validationMessages: {
...(formScreen.validationMessages ?? {}),
...updates,
},
});
};
const addField = () => {
const newField: FormFieldDefinition = {
id: `field_${Date.now()}`,
label: "Новое поле",
placeholder: "Введите значение",
type: "text",
required: true,
};
onUpdate({
fields: [...(formScreen.fields || []), newField],
});
};
const removeField = (index: number) => {
const newFields = formScreen.fields?.filter((_, i) => i !== index) || [];
onUpdate({ fields: newFields });
};
return (
<div className="space-y-4">
<div className="space-y-3">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold uppercase tracking-wide text-muted-foreground">Поля формы</h3>
<Button onClick={addField} variant="outline" className="h-8 w-8 p-0 flex items-center justify-center">
<Plus className="h-4 w-4" />
</Button>
</div>
{formScreen.fields?.map((field, index) => (
<div key={field.id} className="space-y-2 rounded-lg border border-border/50 bg-muted/5 p-3">
<div className="flex items-center justify-between gap-2">
<span className="text-xs font-semibold uppercase text-muted-foreground">
Поле {index + 1}
</span>
<Button
variant="ghost"
className="h-8 px-3 text-xs text-destructive"
onClick={() => removeField(index)}
>
Удалить
</Button>
</div>
<div className="grid grid-cols-2 gap-3 text-xs">
<label className="flex flex-col gap-1 text-muted-foreground">
ID поля
<TextInput value={field.id} onChange={(event) => updateField(index, { id: event.target.value })} />
</label>
<label className="flex flex-col gap-1 text-muted-foreground">
Тип
<select
className="rounded-lg border border-border bg-background px-2 py-1"
value={field.type ?? "text"}
onChange={(event) => updateField(index, { type: event.target.value as FormFieldDefinition["type"] })}
>
<option value="text">Текст</option>
<option value="email">E-mail</option>
<option value="tel">Телефон</option>
<option value="url">Ссылка</option>
</select>
</label>
</div>
<label className="flex flex-col gap-1 text-xs text-muted-foreground">
Метка поля
<TextInput
value={field.label ?? ""}
onChange={(event) => updateField(index, { label: event.target.value })}
/>
</label>
<label className="flex flex-col gap-1 text-xs text-muted-foreground">
Placeholder
<TextInput
value={field.placeholder ?? ""}
onChange={(event) => updateField(index, { placeholder: event.target.value })}
/>
</label>
<div className="grid grid-cols-2 gap-3 text-xs">
<label className="flex items-center gap-2 text-sm text-muted-foreground">
<input
type="checkbox"
checked={field.required ?? false}
onChange={(event) => updateField(index, { required: event.target.checked })}
/>
Обязательно для заполнения
</label>
<label className="flex flex-col gap-1 text-muted-foreground">
Максимальная длина
<input
type="number"
min={1}
className="rounded-lg border border-border bg-background px-2 py-1"
value={field.maxLength ?? ""}
onChange={(event) =>
updateField(index, {
maxLength: event.target.value ? Number(event.target.value) : undefined,
})
}
/>
</label>
</div>
<div className="grid grid-cols-2 gap-3 text-xs">
<label className="flex flex-col gap-1 text-muted-foreground">
Регулярное выражение (pattern)
<TextInput
placeholder="Например, ^\\d+$"
value={field.validation?.pattern ?? ""}
onChange={(event) =>
updateField(index, {
validation: {
...(field.validation ?? {}),
pattern: event.target.value || undefined,
message: field.validation?.message,
},
})
}
/>
</label>
<label className="flex flex-col gap-1 text-muted-foreground">
Текст ошибки для pattern
<TextInput
placeholder="Неверный формат"
value={field.validation?.message ?? ""}
onChange={(event) =>
updateField(index, {
validation:
field.validation || event.target.value
? {
...(field.validation ?? {}),
message: event.target.value || undefined,
}
: undefined,
})
}
/>
</label>
</div>
</div>
))}
{(!formScreen.fields || formScreen.fields.length === 0) && (
<div className="rounded-lg border border-dashed border-border/60 p-4 text-center text-sm text-muted-foreground">
Пока нет полей. Добавьте хотя бы одно, чтобы форма работала.
</div>
)}
</div>
<div className="space-y-3">
<h4 className="text-sm font-semibold text-foreground">Сообщения валидации</h4>
<div className="space-y-4 text-xs">
<div className="rounded-lg border border-border/60 bg-muted/20 p-3">
<label className="flex flex-col gap-2 text-muted-foreground">
<div>
<span className="font-medium">Обязательное поле</span>
<p className="text-xs mt-1 text-muted-foreground/80">
Доступна переменная: <code className="bg-muted px-1 rounded">{`{field}`}</code> - название поля
</p>
</div>
<TextInput
placeholder="Пример: {field} обязательно для заполнения"
value={formScreen.validationMessages?.required ?? ""}
onChange={(event) => updateValidationMessages({ required: event.target.value || undefined })}
/>
</label>
</div>
<div className="rounded-lg border border-border/60 bg-muted/20 p-3">
<label className="flex flex-col gap-2 text-muted-foreground">
<div>
<span className="font-medium">Превышена длина</span>
<p className="text-xs mt-1 text-muted-foreground/80">
Доступны переменные: <code className="bg-muted px-1 rounded">{`{field}`}</code>, <code className="bg-muted px-1 rounded">{`{maxLength}`}</code>
</p>
</div>
<TextInput
placeholder="Пример: {field} не может быть длиннее {maxLength} символов"
value={formScreen.validationMessages?.maxLength ?? ""}
onChange={(event) => updateValidationMessages({ maxLength: event.target.value || undefined })}
/>
</label>
</div>
<div className="rounded-lg border border-border/60 bg-muted/20 p-3">
<label className="flex flex-col gap-2 text-muted-foreground">
<div>
<span className="font-medium">Неверный формат</span>
<p className="text-xs mt-1 text-muted-foreground/80">
Доступна переменная: <code className="bg-muted px-1 rounded">{`{field}`}</code> - название поля
</p>
</div>
<TextInput
placeholder="Пример: Проверьте формат {field}"
value={formScreen.validationMessages?.invalidFormat ?? ""}
onChange={(event) => updateValidationMessages({ invalidFormat: event.target.value || undefined })}
/>
</label>
</div>
</div>
</div>
</div>
);
}