редактор элементов изменен
This commit is contained in:
@@ -58,31 +58,6 @@ export const buttonGroupDefinition: ElementDefinition<
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-sm font-medium">Подсказка</label>
|
|
||||||
<Input
|
|
||||||
placeholder="Выберите значение..."
|
|
||||||
value={config.placeholder || ''}
|
|
||||||
onChange={(e) =>
|
|
||||||
onChange({ ...config, placeholder: e.target.value })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="required"
|
|
||||||
checked={config.required}
|
|
||||||
onChange={(e) =>
|
|
||||||
onChange({ ...config, required: e.target.checked })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label htmlFor="required" className="text-sm font-medium">
|
|
||||||
Обязательное поле
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium">Расположение кнопок</label>
|
<label className="text-sm font-medium">Расположение кнопок</label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
|
|||||||
@@ -1,54 +1,33 @@
|
|||||||
import { Input } from "@/components/ui/input";
|
import { ElementDefinition } from '@/lib/element-registry'
|
||||||
import { ElementDefinition } from "@/lib/element-registry";
|
import { CheckSquare } from 'lucide-react'
|
||||||
import { CheckSquare } from "lucide-react";
|
|
||||||
|
|
||||||
interface CheckboxConfig {
|
interface CheckboxConfig {
|
||||||
placeholder?: string;
|
placeholder?: string
|
||||||
required?: boolean;
|
required?: boolean
|
||||||
targetCells: any[];
|
targetCells: any[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const checkboxDefinition: ElementDefinition<CheckboxConfig, boolean> = {
|
export const checkboxDefinition: ElementDefinition<CheckboxConfig, boolean> = {
|
||||||
type: "checkbox",
|
type: 'checkbox',
|
||||||
label: "Чекбокс",
|
label: 'Чекбокс',
|
||||||
icon: <CheckSquare className="h-4 w-4" />,
|
icon: <CheckSquare className="h-4 w-4" />,
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
placeholder: "",
|
placeholder: '',
|
||||||
required: false,
|
required: false,
|
||||||
targetCells: [],
|
targetCells: [],
|
||||||
},
|
},
|
||||||
Editor: ({ config, onChange }) => (
|
Editor: ({ config, onChange }) => (
|
||||||
<div className="space-y-4">
|
<div className="text-sm text-gray-600">
|
||||||
<div className="space-y-2">
|
Дополнительные настройки отсутствуют
|
||||||
<label className="text-sm font-medium">Подсказка</label>
|
|
||||||
<Input
|
|
||||||
placeholder="Отметить"
|
|
||||||
value={config.placeholder || ""}
|
|
||||||
onChange={(e) => onChange({ ...config, placeholder: e.target.value })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="required"
|
|
||||||
checked={config.required}
|
|
||||||
onChange={(e) => onChange({ ...config, required: e.target.checked })}
|
|
||||||
/>
|
|
||||||
<label htmlFor="required" className="text-sm font-medium">
|
|
||||||
Обязательное поле
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
Preview: ({ config }) => (
|
Preview: ({ config }) => (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="text-sm font-medium text-gray-700">Предпросмотр</div>
|
<div className="text-sm font-medium text-gray-700">Предпросмотр</div>
|
||||||
<div className="p-4 border border-gray-200 rounded-lg bg-white">
|
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<div className="w-4 h-4 border border-gray-300 rounded" />
|
<div className="h-4 w-4 rounded border border-gray-300" />
|
||||||
<span className="text-sm">
|
<span className="text-sm">{config.placeholder || 'Отметить'}</span>
|
||||||
{config.placeholder || "Отметить"}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -60,11 +39,9 @@ export const checkboxDefinition: ElementDefinition<CheckboxConfig, boolean> = {
|
|||||||
checked={value || false}
|
checked={value || false}
|
||||||
onChange={(e) => onChange?.(e.target.checked)}
|
onChange={(e) => onChange?.(e.target.checked)}
|
||||||
required={config.required}
|
required={config.required}
|
||||||
className="w-4 h-4"
|
className="h-4 w-4"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm">
|
<span className="text-sm">{config.placeholder || 'Отметить'}</span>
|
||||||
{config.placeholder || "Отметить"}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from '@/components/ui/input'
|
||||||
import { ElementDefinition } from "@/lib/element-registry";
|
import { ElementDefinition } from '@/lib/element-registry'
|
||||||
import { Calendar } from "lucide-react";
|
import { Calendar } from 'lucide-react'
|
||||||
|
|
||||||
interface DateConfig {
|
interface DateConfig {
|
||||||
required?: boolean;
|
required?: boolean
|
||||||
targetCells: any[];
|
targetCells: any[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const dateDefinition: ElementDefinition<DateConfig, string> = {
|
export const dateDefinition: ElementDefinition<DateConfig, string> = {
|
||||||
type: "date",
|
type: 'date',
|
||||||
label: "Дата",
|
label: 'Дата',
|
||||||
icon: <Calendar className="h-4 w-4" />,
|
icon: <Calendar className="h-4 w-4" />,
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
required: false,
|
required: false,
|
||||||
@@ -33,21 +33,17 @@ export const dateDefinition: ElementDefinition<DateConfig, string> = {
|
|||||||
Preview: () => (
|
Preview: () => (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="text-sm font-medium text-gray-700">Предпросмотр</div>
|
<div className="text-sm font-medium text-gray-700">Предпросмотр</div>
|
||||||
<div className="p-4 border border-gray-200 rounded-lg bg-white">
|
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
||||||
<Input
|
<Input type="date" disabled className="w-full" />
|
||||||
type="date"
|
|
||||||
disabled
|
|
||||||
className="w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
Render: ({ config, value, onChange }) => (
|
Render: ({ config, value, onChange }) => (
|
||||||
<Input
|
<Input
|
||||||
type="date"
|
type="date"
|
||||||
value={value || ""}
|
value={value || ''}
|
||||||
onChange={(e) => onChange?.(e.target.value)}
|
onChange={(e) => onChange?.(e.target.value)}
|
||||||
required={config.required}
|
required={config.required}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -1,52 +1,34 @@
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from '@/components/ui/input'
|
||||||
import { ElementDefinition } from "@/lib/element-registry";
|
import { ElementDefinition } from '@/lib/element-registry'
|
||||||
import { Hash } from "lucide-react";
|
import { Hash } from 'lucide-react'
|
||||||
|
|
||||||
interface NumberConfig {
|
interface NumberConfig {
|
||||||
placeholder?: string;
|
placeholder?: string
|
||||||
required?: boolean;
|
required?: boolean
|
||||||
targetCells: any[];
|
targetCells: any[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const numberDefinition: ElementDefinition<NumberConfig, number> = {
|
export const numberDefinition: ElementDefinition<NumberConfig, number> = {
|
||||||
type: "number",
|
type: 'number',
|
||||||
label: "Число",
|
label: 'Число',
|
||||||
icon: <Hash className="h-4 w-4" />,
|
icon: <Hash className="h-4 w-4" />,
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
placeholder: "",
|
placeholder: '',
|
||||||
required: false,
|
required: false,
|
||||||
targetCells: [],
|
targetCells: [],
|
||||||
},
|
},
|
||||||
Editor: ({ config, onChange }) => (
|
Editor: ({ config, onChange }) => (
|
||||||
<div className="space-y-4">
|
<div className="text-sm text-gray-600">
|
||||||
<div className="space-y-2">
|
Дополнительные настройки отсутствуют
|
||||||
<label className="text-sm font-medium">Подсказка</label>
|
|
||||||
<Input
|
|
||||||
placeholder="0"
|
|
||||||
value={config.placeholder || ""}
|
|
||||||
onChange={(e) => onChange({ ...config, placeholder: e.target.value })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="required"
|
|
||||||
checked={config.required}
|
|
||||||
onChange={(e) => onChange({ ...config, required: e.target.checked })}
|
|
||||||
/>
|
|
||||||
<label htmlFor="required" className="text-sm font-medium">
|
|
||||||
Обязательное поле
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
Preview: ({ config }) => (
|
Preview: ({ config }) => (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="text-sm font-medium text-gray-700">Предпросмотр</div>
|
<div className="text-sm font-medium text-gray-700">Предпросмотр</div>
|
||||||
<div className="p-4 border border-gray-200 rounded-lg bg-white">
|
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
placeholder={config.placeholder || "0"}
|
placeholder={config.placeholder || '0'}
|
||||||
disabled
|
disabled
|
||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
@@ -56,10 +38,10 @@ export const numberDefinition: ElementDefinition<NumberConfig, number> = {
|
|||||||
Render: ({ config, value, onChange }) => (
|
Render: ({ config, value, onChange }) => (
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
placeholder={config.placeholder || "0"}
|
placeholder={config.placeholder || '0'}
|
||||||
value={value || ""}
|
value={value || ''}
|
||||||
onChange={(e) => onChange?.(parseFloat(e.target.value) || 0)}
|
onChange={(e) => onChange?.(parseFloat(e.target.value) || 0)}
|
||||||
required={config.required}
|
required={config.required}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from '@/components/ui/button'
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from '@/components/ui/input'
|
||||||
import { ElementDefinition } from "@/lib/element-registry";
|
import { ElementDefinition } from '@/lib/element-registry'
|
||||||
import { ElementOption } from "@/types/template";
|
import { ElementOption } from '@/types/template'
|
||||||
import { CheckSquare, Plus, Trash2 } from "lucide-react";
|
import { CheckSquare, Plus, Trash2 } from 'lucide-react'
|
||||||
|
|
||||||
interface RadioConfig {
|
interface RadioConfig {
|
||||||
placeholder?: string;
|
placeholder?: string
|
||||||
required?: boolean;
|
required?: boolean
|
||||||
options: ElementOption[];
|
options: ElementOption[]
|
||||||
targetCells: any[];
|
targetCells: any[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const radioDefinition: ElementDefinition<RadioConfig, string> = {
|
export const radioDefinition: ElementDefinition<RadioConfig, string> = {
|
||||||
type: "radio",
|
type: 'radio',
|
||||||
label: "Радиокнопки",
|
label: 'Радиокнопки',
|
||||||
icon: <CheckSquare className="h-4 w-4" />,
|
icon: <CheckSquare className="h-4 w-4" />,
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
placeholder: "",
|
placeholder: '',
|
||||||
required: false,
|
required: false,
|
||||||
options: [],
|
options: [],
|
||||||
targetCells: [],
|
targetCells: [],
|
||||||
@@ -26,70 +26,61 @@ export const radioDefinition: ElementDefinition<RadioConfig, string> = {
|
|||||||
onChange({
|
onChange({
|
||||||
...config,
|
...config,
|
||||||
options: [...config.options, { value: '', label: '' }],
|
options: [...config.options, { value: '', label: '' }],
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleUpdateOption = (index: number, field: 'value' | 'label', value: string) => {
|
const handleUpdateOption = (
|
||||||
|
index: number,
|
||||||
|
field: 'value' | 'label',
|
||||||
|
value: string,
|
||||||
|
) => {
|
||||||
onChange({
|
onChange({
|
||||||
...config,
|
...config,
|
||||||
options: config.options.map((option, i) =>
|
options: config.options.map((option, i) =>
|
||||||
i === index ? { ...option, [field]: value } : option
|
i === index ? { ...option, [field]: value } : option,
|
||||||
),
|
),
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleRemoveOption = (index: number) => {
|
const handleRemoveOption = (index: number) => {
|
||||||
onChange({
|
onChange({
|
||||||
...config,
|
...config,
|
||||||
options: config.options.filter((_, i) => i !== index),
|
options: config.options.filter((_, i) => i !== index),
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-sm font-medium">Подсказка</label>
|
|
||||||
<Input
|
|
||||||
placeholder="Выберите вариант..."
|
|
||||||
value={config.placeholder || ""}
|
|
||||||
onChange={(e) => onChange({ ...config, placeholder: e.target.value })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="required"
|
|
||||||
checked={config.required}
|
|
||||||
onChange={(e) => onChange({ ...config, required: e.target.checked })}
|
|
||||||
/>
|
|
||||||
<label htmlFor="required" className="text-sm font-medium">
|
|
||||||
Обязательное поле
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<label className="text-sm font-medium">Варианты</label>
|
<label className="text-sm font-medium">Варианты</label>
|
||||||
<Button variant="outline" size="sm" onClick={handleAddOption}>
|
<Button variant="outline" size="sm" onClick={handleAddOption}>
|
||||||
<Plus className="h-4 w-4 mr-1" />
|
<Plus className="mr-1 h-4 w-4" />
|
||||||
Добавить
|
Добавить
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2 max-h-32 overflow-y-auto">
|
<div className="max-h-32 space-y-2 overflow-y-auto">
|
||||||
{config.options.map((option, index) => (
|
{config.options.map((option, index) => (
|
||||||
<div key={index} className="flex gap-2">
|
<div key={index} className="flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
placeholder="Значение"
|
placeholder="Значение"
|
||||||
value={option.value}
|
value={option.value}
|
||||||
onChange={(e) => handleUpdateOption(index, 'value', e.target.value)}
|
onChange={(e) =>
|
||||||
|
handleUpdateOption(index, 'value', e.target.value)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Подпись"
|
placeholder="Подпись"
|
||||||
value={option.label}
|
value={option.label}
|
||||||
onChange={(e) => handleUpdateOption(index, 'label', e.target.value)}
|
onChange={(e) =>
|
||||||
|
handleUpdateOption(index, 'label', e.target.value)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Button variant="outline" size="icon" onClick={() => handleRemoveOption(index)}>
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => handleRemoveOption(index)}
|
||||||
|
>
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -97,21 +88,23 @@ export const radioDefinition: ElementDefinition<RadioConfig, string> = {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
},
|
},
|
||||||
Preview: ({ config }) => (
|
Preview: ({ config }) => (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="text-sm font-medium text-gray-700">Предпросмотр</div>
|
<div className="text-sm font-medium text-gray-700">Предпросмотр</div>
|
||||||
<div className="p-4 border border-gray-200 rounded-lg bg-white">
|
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{config.options.length > 0 ? config.options.map((option, index) => (
|
{config.options.length > 0 ? (
|
||||||
<div key={index} className="flex items-center space-x-2">
|
config.options.map((option, index) => (
|
||||||
<div className="w-4 h-4 border border-gray-300 rounded-full" />
|
<div key={index} className="flex items-center space-x-2">
|
||||||
<span className="text-sm">{option.label}</span>
|
<div className="h-4 w-4 rounded-full border border-gray-300" />
|
||||||
</div>
|
<span className="text-sm">{option.label}</span>
|
||||||
)) : (
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<div className="w-4 h-4 border border-gray-300 rounded-full" />
|
<div className="h-4 w-4 rounded-full border border-gray-300" />
|
||||||
<span className="text-sm text-gray-500">Вариант 1</span>
|
<span className="text-sm text-gray-500">Вариант 1</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -130,11 +123,11 @@ export const radioDefinition: ElementDefinition<RadioConfig, string> = {
|
|||||||
checked={value === option.value}
|
checked={value === option.value}
|
||||||
onChange={(e) => onChange?.(e.target.value)}
|
onChange={(e) => onChange?.(e.target.value)}
|
||||||
required={config.required}
|
required={config.required}
|
||||||
className="w-4 h-4"
|
className="h-4 w-4"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm">{option.label}</span>
|
<span className="text-sm">{option.label}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -1,23 +1,29 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from '@/components/ui/button'
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from '@/components/ui/input'
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import {
|
||||||
import { ElementDefinition } from "@/lib/element-registry";
|
Select,
|
||||||
import { ElementOption } from "@/types/template";
|
SelectContent,
|
||||||
import { ChevronDown, Plus, Trash2 } from "lucide-react";
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select'
|
||||||
|
import { ElementDefinition } from '@/lib/element-registry'
|
||||||
|
import { ElementOption } from '@/types/template'
|
||||||
|
import { ChevronDown, Plus, Trash2 } from 'lucide-react'
|
||||||
|
|
||||||
interface SelectConfig {
|
interface SelectConfig {
|
||||||
placeholder?: string;
|
placeholder?: string
|
||||||
required?: boolean;
|
required?: boolean
|
||||||
options: ElementOption[];
|
options: ElementOption[]
|
||||||
targetCells: any[];
|
targetCells: any[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const selectDefinition: ElementDefinition<SelectConfig, string> = {
|
export const selectDefinition: ElementDefinition<SelectConfig, string> = {
|
||||||
type: "select",
|
type: 'select',
|
||||||
label: "Выпадающий список",
|
label: 'Выпадающий список',
|
||||||
icon: <ChevronDown className="h-4 w-4" />,
|
icon: <ChevronDown className="h-4 w-4" />,
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
placeholder: "",
|
placeholder: '',
|
||||||
required: false,
|
required: false,
|
||||||
options: [],
|
options: [],
|
||||||
targetCells: [],
|
targetCells: [],
|
||||||
@@ -27,70 +33,61 @@ export const selectDefinition: ElementDefinition<SelectConfig, string> = {
|
|||||||
onChange({
|
onChange({
|
||||||
...config,
|
...config,
|
||||||
options: [...config.options, { value: '', label: '' }],
|
options: [...config.options, { value: '', label: '' }],
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleUpdateOption = (index: number, field: 'value' | 'label', value: string) => {
|
const handleUpdateOption = (
|
||||||
|
index: number,
|
||||||
|
field: 'value' | 'label',
|
||||||
|
value: string,
|
||||||
|
) => {
|
||||||
onChange({
|
onChange({
|
||||||
...config,
|
...config,
|
||||||
options: config.options.map((option, i) =>
|
options: config.options.map((option, i) =>
|
||||||
i === index ? { ...option, [field]: value } : option
|
i === index ? { ...option, [field]: value } : option,
|
||||||
),
|
),
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleRemoveOption = (index: number) => {
|
const handleRemoveOption = (index: number) => {
|
||||||
onChange({
|
onChange({
|
||||||
...config,
|
...config,
|
||||||
options: config.options.filter((_, i) => i !== index),
|
options: config.options.filter((_, i) => i !== index),
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-sm font-medium">Подсказка</label>
|
|
||||||
<Input
|
|
||||||
placeholder="Выберите значение..."
|
|
||||||
value={config.placeholder || ""}
|
|
||||||
onChange={(e) => onChange({ ...config, placeholder: e.target.value })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="required"
|
|
||||||
checked={config.required}
|
|
||||||
onChange={(e) => onChange({ ...config, required: e.target.checked })}
|
|
||||||
/>
|
|
||||||
<label htmlFor="required" className="text-sm font-medium">
|
|
||||||
Обязательное поле
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<label className="text-sm font-medium">Варианты</label>
|
<label className="text-sm font-medium">Варианты</label>
|
||||||
<Button variant="outline" size="sm" onClick={handleAddOption}>
|
<Button variant="outline" size="sm" onClick={handleAddOption}>
|
||||||
<Plus className="h-4 w-4 mr-1" />
|
<Plus className="mr-1 h-4 w-4" />
|
||||||
Добавить
|
Добавить
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2 max-h-32 overflow-y-auto">
|
<div className="max-h-32 space-y-2 overflow-y-auto">
|
||||||
{config.options.map((option, index) => (
|
{config.options.map((option, index) => (
|
||||||
<div key={index} className="flex gap-2">
|
<div key={index} className="flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
placeholder="Значение"
|
placeholder="Значение"
|
||||||
value={option.value}
|
value={option.value}
|
||||||
onChange={(e) => handleUpdateOption(index, 'value', e.target.value)}
|
onChange={(e) =>
|
||||||
|
handleUpdateOption(index, 'value', e.target.value)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Подпись"
|
placeholder="Подпись"
|
||||||
value={option.label}
|
value={option.label}
|
||||||
onChange={(e) => handleUpdateOption(index, 'label', e.target.value)}
|
onChange={(e) =>
|
||||||
|
handleUpdateOption(index, 'label', e.target.value)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Button variant="outline" size="icon" onClick={() => handleRemoveOption(index)}>
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => handleRemoveOption(index)}
|
||||||
|
>
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -98,16 +95,16 @@ export const selectDefinition: ElementDefinition<SelectConfig, string> = {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
},
|
},
|
||||||
Preview: ({ config }) => (
|
Preview: ({ config }) => (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="text-sm font-medium text-gray-700">Предпросмотр</div>
|
<div className="text-sm font-medium text-gray-700">Предпросмотр</div>
|
||||||
<div className="p-4 border border-gray-200 rounded-lg bg-white">
|
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
||||||
<Select disabled>
|
<Select disabled>
|
||||||
<SelectTrigger className="w-full">
|
<SelectTrigger className="w-full">
|
||||||
<span className="text-gray-500">
|
<span className="text-gray-500">
|
||||||
{config.placeholder || "Выберите значение"}
|
{config.placeholder || 'Выберите значение'}
|
||||||
</span>
|
</span>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
</Select>
|
</Select>
|
||||||
@@ -117,7 +114,7 @@ export const selectDefinition: ElementDefinition<SelectConfig, string> = {
|
|||||||
Render: ({ config, value, onChange }) => (
|
Render: ({ config, value, onChange }) => (
|
||||||
<Select value={value} onValueChange={onChange}>
|
<Select value={value} onValueChange={onChange}>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder={config.placeholder || "Выберите значение"} />
|
<SelectValue placeholder={config.placeholder || 'Выберите значение'} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{config.options.map((option, index) => (
|
{config.options.map((option, index) => (
|
||||||
@@ -128,4 +125,4 @@ export const selectDefinition: ElementDefinition<SelectConfig, string> = {
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
),
|
),
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -1,51 +1,33 @@
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from '@/components/ui/input'
|
||||||
import { ElementDefinition } from "@/lib/element-registry";
|
import { ElementDefinition } from '@/lib/element-registry'
|
||||||
import { Type } from "lucide-react";
|
import { Type } from 'lucide-react'
|
||||||
|
|
||||||
interface TextConfig {
|
interface TextConfig {
|
||||||
placeholder?: string;
|
placeholder?: string
|
||||||
required?: boolean;
|
required?: boolean
|
||||||
targetCells: any[];
|
targetCells: any[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const textDefinition: ElementDefinition<TextConfig, string> = {
|
export const textDefinition: ElementDefinition<TextConfig, string> = {
|
||||||
type: "text",
|
type: 'text',
|
||||||
label: "Текстовое поле",
|
label: 'Текстовое поле',
|
||||||
icon: <Type className="h-4 w-4" />,
|
icon: <Type className="h-4 w-4" />,
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
placeholder: "",
|
placeholder: '',
|
||||||
required: false,
|
required: false,
|
||||||
targetCells: [],
|
targetCells: [],
|
||||||
},
|
},
|
||||||
Editor: ({ config, onChange }) => (
|
Editor: ({ config, onChange }) => (
|
||||||
<div className="space-y-4">
|
<div className="text-sm text-gray-600">
|
||||||
<div className="space-y-2">
|
Дополнительные настройки отсутствуют
|
||||||
<label className="text-sm font-medium">Подсказка</label>
|
|
||||||
<Input
|
|
||||||
placeholder="Введите текст..."
|
|
||||||
value={config.placeholder || ""}
|
|
||||||
onChange={(e) => onChange({ ...config, placeholder: e.target.value })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="required"
|
|
||||||
checked={config.required}
|
|
||||||
onChange={(e) => onChange({ ...config, required: e.target.checked })}
|
|
||||||
/>
|
|
||||||
<label htmlFor="required" className="text-sm font-medium">
|
|
||||||
Обязательное поле
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
Preview: ({ config }) => (
|
Preview: ({ config }) => (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="text-sm font-medium text-gray-700">Предпросмотр</div>
|
<div className="text-sm font-medium text-gray-700">Предпросмотр</div>
|
||||||
<div className="p-4 border border-gray-200 rounded-lg bg-white">
|
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
||||||
<Input
|
<Input
|
||||||
placeholder={config.placeholder || "Введите текст"}
|
placeholder={config.placeholder || 'Введите текст'}
|
||||||
disabled
|
disabled
|
||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
@@ -54,10 +36,10 @@ export const textDefinition: ElementDefinition<TextConfig, string> = {
|
|||||||
),
|
),
|
||||||
Render: ({ config, value, onChange }) => (
|
Render: ({ config, value, onChange }) => (
|
||||||
<Input
|
<Input
|
||||||
placeholder={config.placeholder || "Введите текст"}
|
placeholder={config.placeholder || 'Введите текст'}
|
||||||
value={value || ""}
|
value={value || ''}
|
||||||
onChange={(e) => onChange?.(e.target.value)}
|
onChange={(e) => onChange?.(e.target.value)}
|
||||||
required={config.required}
|
required={config.required}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -1,52 +1,33 @@
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Textarea } from '@/components/ui/textarea'
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { ElementDefinition } from '@/lib/element-registry'
|
||||||
import { ElementDefinition } from "@/lib/element-registry";
|
import { FileText } from 'lucide-react'
|
||||||
import { FileText } from "lucide-react";
|
|
||||||
|
|
||||||
interface TextareaConfig {
|
interface TextareaConfig {
|
||||||
placeholder?: string;
|
placeholder?: string
|
||||||
required?: boolean;
|
required?: boolean
|
||||||
targetCells: any[];
|
targetCells: any[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const textareaDefinition: ElementDefinition<TextareaConfig, string> = {
|
export const textareaDefinition: ElementDefinition<TextareaConfig, string> = {
|
||||||
type: "textarea",
|
type: 'textarea',
|
||||||
label: "Многострочный текст",
|
label: 'Многострочный текст',
|
||||||
icon: <FileText className="h-4 w-4" />,
|
icon: <FileText className="h-4 w-4" />,
|
||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
placeholder: "",
|
placeholder: '',
|
||||||
required: false,
|
required: false,
|
||||||
targetCells: [],
|
targetCells: [],
|
||||||
},
|
},
|
||||||
Editor: ({ config, onChange }) => (
|
Editor: ({ config, onChange }) => (
|
||||||
<div className="space-y-4">
|
<div className="text-sm text-gray-600">
|
||||||
<div className="space-y-2">
|
Дополнительные настройки отсутствуют
|
||||||
<label className="text-sm font-medium">Подсказка</label>
|
|
||||||
<Input
|
|
||||||
placeholder="Введите текст..."
|
|
||||||
value={config.placeholder || ""}
|
|
||||||
onChange={(e) => onChange({ ...config, placeholder: e.target.value })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="required"
|
|
||||||
checked={config.required}
|
|
||||||
onChange={(e) => onChange({ ...config, required: e.target.checked })}
|
|
||||||
/>
|
|
||||||
<label htmlFor="required" className="text-sm font-medium">
|
|
||||||
Обязательное поле
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
Preview: ({ config }) => (
|
Preview: ({ config }) => (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="text-sm font-medium text-gray-700">Предпросмотр</div>
|
<div className="text-sm font-medium text-gray-700">Предпросмотр</div>
|
||||||
<div className="p-4 border border-gray-200 rounded-lg bg-white">
|
<div className="rounded-lg border border-gray-200 bg-white p-4">
|
||||||
<Textarea
|
<Textarea
|
||||||
placeholder={config.placeholder || "Введите текст"}
|
placeholder={config.placeholder || 'Введите текст'}
|
||||||
disabled
|
disabled
|
||||||
rows={3}
|
rows={3}
|
||||||
className="w-full resize-none"
|
className="w-full resize-none"
|
||||||
@@ -56,12 +37,12 @@ export const textareaDefinition: ElementDefinition<TextareaConfig, string> = {
|
|||||||
),
|
),
|
||||||
Render: ({ config, value, onChange }) => (
|
Render: ({ config, value, onChange }) => (
|
||||||
<Textarea
|
<Textarea
|
||||||
placeholder={config.placeholder || "Введите текст"}
|
placeholder={config.placeholder || 'Введите текст'}
|
||||||
value={value || ""}
|
value={value || ''}
|
||||||
onChange={(e) => onChange?.(e.target.value)}
|
onChange={(e) => onChange?.(e.target.value)}
|
||||||
required={config.required}
|
required={config.required}
|
||||||
rows={3}
|
rows={3}
|
||||||
className="w-full resize-none"
|
className="w-full resize-none"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ const CellTargetEditor: React.FC<{
|
|||||||
onChange: (targets: CellTarget[]) => void
|
onChange: (targets: CellTarget[]) => void
|
||||||
}> = ({ targets, onChange }) => {
|
}> = ({ targets, onChange }) => {
|
||||||
const handleAddTarget = () => {
|
const handleAddTarget = () => {
|
||||||
onChange([...targets, { sheet: 'L', cell: '', displayName: '' }])
|
onChange([...targets, { sheet: 'L', cell: '' }])
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUpdateTarget = (
|
const handleUpdateTarget = (
|
||||||
@@ -77,19 +77,24 @@ const CellTargetEditor: React.FC<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-1.5">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<label className="text-sm font-medium">Целевые ячейки</label>
|
<label className="text-sm font-medium">Целевые ячейки</label>
|
||||||
<Button variant="outline" size="sm" onClick={handleAddTarget}>
|
<Button
|
||||||
<Plus className="mr-1 h-4 w-4" />
|
variant="outline"
|
||||||
Добавить ячейку
|
size="sm"
|
||||||
|
onClick={handleAddTarget}
|
||||||
|
className="h-7 px-2 text-xs"
|
||||||
|
>
|
||||||
|
<Plus className="mr-1 h-3 w-3" />
|
||||||
|
Добавить
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="max-h-48 space-y-3 overflow-y-auto">
|
<div className="max-h-40 space-y-2 overflow-y-auto">
|
||||||
{targets.map((target, index) => (
|
{targets.map((target, index) => (
|
||||||
<div key={index} className="rounded-lg border bg-gray-50 p-3">
|
<div key={index} className="rounded border bg-gray-50/80 p-2">
|
||||||
<div className="mb-2 grid grid-cols-3 gap-2">
|
<div className="mb-1.5 grid grid-cols-2 gap-2">
|
||||||
<div className="space-y-1">
|
<div>
|
||||||
<label className="text-xs font-medium text-gray-600">
|
<label className="text-xs font-medium text-gray-600">
|
||||||
Лист
|
Лист
|
||||||
</label>
|
</label>
|
||||||
@@ -99,7 +104,7 @@ const CellTargetEditor: React.FC<{
|
|||||||
handleUpdateTarget(index, 'sheet', value)
|
handleUpdateTarget(index, 'sheet', value)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="h-8">
|
<SelectTrigger className="h-7 text-xs">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@@ -110,7 +115,7 @@ const CellTargetEditor: React.FC<{
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div>
|
||||||
<label className="text-xs font-medium text-gray-600">
|
<label className="text-xs font-medium text-gray-600">
|
||||||
Ячейка
|
Ячейка
|
||||||
</label>
|
</label>
|
||||||
@@ -120,36 +125,23 @@ const CellTargetEditor: React.FC<{
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleUpdateTarget(index, 'cell', e.target.value)
|
handleUpdateTarget(index, 'cell', e.target.value)
|
||||||
}
|
}
|
||||||
className="h-8"
|
className="h-7 text-xs"
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<label className="text-xs font-medium text-gray-600">
|
|
||||||
Название
|
|
||||||
</label>
|
|
||||||
<Input
|
|
||||||
placeholder="Описание"
|
|
||||||
value={target.displayName || ''}
|
|
||||||
onChange={(e) =>
|
|
||||||
handleUpdateTarget(index, 'displayName', e.target.value)
|
|
||||||
}
|
|
||||||
className="h-8"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => handleRemoveTarget(index)}
|
onClick={() => handleRemoveTarget(index)}
|
||||||
className="h-6 text-xs text-red-600 hover:text-red-700"
|
className="h-5 px-1 text-xs text-red-600 hover:bg-red-50 hover:text-red-700"
|
||||||
>
|
>
|
||||||
<Trash2 className="mr-1 h-3 w-3" />
|
<Trash2 className="mr-1 h-2.5 w-2.5" />
|
||||||
Удалить
|
Удалить
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{targets.length === 0 && (
|
{targets.length === 0 && (
|
||||||
<p className="py-4 text-center text-sm text-gray-500">
|
<p className="py-3 text-center text-xs text-gray-500">
|
||||||
Добавьте ячейки для связи с элементом
|
Добавьте ячейки для связи с элементом
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -344,7 +336,6 @@ const ElementPreview: React.FC<{ element: Partial<TemplateElement> }> = ({
|
|||||||
<span
|
<span
|
||||||
key={index}
|
key={index}
|
||||||
className="inline-block rounded bg-blue-100 px-1.5 py-0.5 text-xs text-blue-700"
|
className="inline-block rounded bg-blue-100 px-1.5 py-0.5 text-xs text-blue-700"
|
||||||
title={target.displayName}
|
|
||||||
>
|
>
|
||||||
{target.sheet}!{target.cell}
|
{target.sheet}!{target.cell}
|
||||||
</span>
|
</span>
|
||||||
@@ -398,8 +389,8 @@ const ElementForm: React.FC<ElementFormProps> = ({ formData, setFormData }) => {
|
|||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-2 gap-6">
|
<div className="grid grid-cols-2 gap-6">
|
||||||
{/* Форма настроек */}
|
{/* Форма настроек */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-1.5">
|
||||||
<label className="text-sm font-medium">Тип элемента</label>
|
<label className="text-sm font-medium">Тип элемента</label>
|
||||||
<Select
|
<Select
|
||||||
value={formData.type}
|
value={formData.type}
|
||||||
@@ -437,7 +428,7 @@ const ElementForm: React.FC<ElementFormProps> = ({ formData, setFormData }) => {
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger className="h-9">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@@ -453,19 +444,8 @@ const ElementForm: React.FC<ElementFormProps> = ({ formData, setFormData }) => {
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div className="space-y-2">
|
<div className="space-y-1.5">
|
||||||
<label className="text-sm font-medium">Имя поля</label>
|
|
||||||
<Input
|
|
||||||
placeholder="field_name"
|
|
||||||
value={formData.name}
|
|
||||||
onChange={(e) =>
|
|
||||||
setFormData((prev) => ({ ...prev, name: e.target.value }))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-sm font-medium">Подпись</label>
|
<label className="text-sm font-medium">Подпись</label>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Название поля"
|
placeholder="Название поля"
|
||||||
@@ -473,8 +453,37 @@ const ElementForm: React.FC<ElementFormProps> = ({ formData, setFormData }) => {
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData((prev) => ({ ...prev, label: e.target.value }))
|
setFormData((prev) => ({ ...prev, label: e.target.value }))
|
||||||
}
|
}
|
||||||
|
className="h-9"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<label className="text-sm font-medium">Подсказка</label>
|
||||||
|
<Input
|
||||||
|
placeholder="Введите значение..."
|
||||||
|
value={formData.placeholder}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
placeholder: e.target.value,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
className="h-9"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-2 py-1">
|
||||||
|
<Checkbox
|
||||||
|
id="required"
|
||||||
|
checked={formData.required}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
setFormData((prev) => ({ ...prev, required: !!checked }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label htmlFor="required" className="text-sm font-medium">
|
||||||
|
Обязательное поле
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CellTargetEditor
|
<CellTargetEditor
|
||||||
@@ -494,7 +503,7 @@ const ElementForm: React.FC<ElementFormProps> = ({ formData, setFormData }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Live Preview */}
|
{/* Live Preview */}
|
||||||
<div className="sticky top-0">
|
<div className="sticky top-0 flex min-h-[50vh] items-center">
|
||||||
<ElementPreview element={formData} />
|
<ElementPreview element={formData} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -505,8 +514,8 @@ const ElementForm: React.FC<ElementFormProps> = ({ formData, setFormData }) => {
|
|||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-2 gap-6">
|
<div className="grid grid-cols-2 gap-6">
|
||||||
{/* Форма настроек */}
|
{/* Форма настроек */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-1.5">
|
||||||
<label className="text-sm font-medium">Тип элемента</label>
|
<label className="text-sm font-medium">Тип элемента</label>
|
||||||
<Select
|
<Select
|
||||||
value={formData.type}
|
value={formData.type}
|
||||||
@@ -549,7 +558,7 @@ const ElementForm: React.FC<ElementFormProps> = ({ formData, setFormData }) => {
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger className="h-9">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@@ -565,54 +574,18 @@ const ElementForm: React.FC<ElementFormProps> = ({ formData, setFormData }) => {
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="space-y-1.5">
|
||||||
<div className="space-y-2">
|
<label className="text-sm font-medium">Подпись</label>
|
||||||
<label className="text-sm font-medium">Имя поля</label>
|
|
||||||
<Input
|
|
||||||
placeholder="field_name"
|
|
||||||
value={formData.name}
|
|
||||||
onChange={(e) =>
|
|
||||||
setFormData((prev) => ({ ...prev, name: e.target.value }))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-sm font-medium">Подпись</label>
|
|
||||||
<Input
|
|
||||||
placeholder="Название поля"
|
|
||||||
value={formData.label}
|
|
||||||
onChange={(e) =>
|
|
||||||
setFormData((prev) => ({ ...prev, label: e.target.value }))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-sm font-medium">Подсказка</label>
|
|
||||||
<Input
|
<Input
|
||||||
placeholder="Введите значение..."
|
placeholder="Название поля"
|
||||||
value={formData.placeholder}
|
value={formData.label}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setFormData((prev) => ({ ...prev, placeholder: e.target.value }))
|
setFormData((prev) => ({ ...prev, label: e.target.value }))
|
||||||
}
|
}
|
||||||
|
className="h-9"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id="required"
|
|
||||||
checked={formData.required}
|
|
||||||
onCheckedChange={(checked) =>
|
|
||||||
setFormData((prev) => ({ ...prev, required: !!checked }))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<label htmlFor="required" className="text-sm font-medium">
|
|
||||||
Обязательное поле
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<CellTargetEditor
|
<CellTargetEditor
|
||||||
targets={formData.targetCells || []}
|
targets={formData.targetCells || []}
|
||||||
onChange={(targets) =>
|
onChange={(targets) =>
|
||||||
@@ -621,23 +594,29 @@ const ElementForm: React.FC<ElementFormProps> = ({ formData, setFormData }) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{needsOptions && (
|
{needsOptions && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-1.5">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<label className="text-sm font-medium">Варианты</label>
|
<label className="text-sm font-medium">Варианты</label>
|
||||||
<Button variant="outline" size="sm" onClick={handleAddOption}>
|
<Button
|
||||||
<Plus className="mr-1 h-4 w-4" />
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleAddOption}
|
||||||
|
className="h-7 px-2 text-xs"
|
||||||
|
>
|
||||||
|
<Plus className="mr-1 h-3 w-3" />
|
||||||
Добавить
|
Добавить
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="max-h-32 space-y-2 overflow-y-auto">
|
<div className="max-h-28 space-y-1.5 overflow-y-auto">
|
||||||
{formData.options?.map((option, index) => (
|
{formData.options?.map((option, index) => (
|
||||||
<div key={index} className="flex gap-2">
|
<div key={index} className="flex gap-1.5">
|
||||||
<Input
|
<Input
|
||||||
placeholder="Значение"
|
placeholder="Значение"
|
||||||
value={option.value}
|
value={option.value}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleUpdateOption(index, 'value', e.target.value)
|
handleUpdateOption(index, 'value', e.target.value)
|
||||||
}
|
}
|
||||||
|
className="h-7 text-xs"
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Подпись"
|
placeholder="Подпись"
|
||||||
@@ -645,13 +624,15 @@ const ElementForm: React.FC<ElementFormProps> = ({ formData, setFormData }) => {
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleUpdateOption(index, 'label', e.target.value)
|
handleUpdateOption(index, 'label', e.target.value)
|
||||||
}
|
}
|
||||||
|
className="h-7 text-xs"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() => handleRemoveOption(index)}
|
onClick={() => handleRemoveOption(index)}
|
||||||
|
className="h-7 w-7 text-red-600 hover:bg-red-50 hover:text-red-700"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -661,7 +642,7 @@ const ElementForm: React.FC<ElementFormProps> = ({ formData, setFormData }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Live Preview */}
|
{/* Live Preview */}
|
||||||
<div className="sticky top-0">
|
<div className="sticky top-0 flex min-h-[50vh] items-center">
|
||||||
<ElementPreview element={formData} />
|
<ElementPreview element={formData} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -755,7 +736,6 @@ export const ElementConstructor: React.FC<ElementConstructorProps> = ({
|
|||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
setFormData({
|
setFormData({
|
||||||
type: 'text',
|
type: 'text',
|
||||||
name: '',
|
|
||||||
label: '',
|
label: '',
|
||||||
targetCells: [],
|
targetCells: [],
|
||||||
placeholder: '',
|
placeholder: '',
|
||||||
@@ -767,8 +747,8 @@ export const ElementConstructor: React.FC<ElementConstructorProps> = ({
|
|||||||
const handleAddElement = () => {
|
const handleAddElement = () => {
|
||||||
console.log('handleAddElement called with formData:', formData)
|
console.log('handleAddElement called with formData:', formData)
|
||||||
|
|
||||||
if (!formData.name || !formData.label) {
|
if (!formData.label) {
|
||||||
console.log('Validation failed: missing name or label')
|
console.log('Validation failed: missing label')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -819,7 +799,7 @@ export const ElementConstructor: React.FC<ElementConstructorProps> = ({
|
|||||||
const newElement: TemplateElement = {
|
const newElement: TemplateElement = {
|
||||||
id: Date.now().toString(),
|
id: Date.now().toString(),
|
||||||
type: formData.type as ElementType,
|
type: formData.type as ElementType,
|
||||||
name: formData.name,
|
name: formData.label?.toLowerCase().replace(/\s+/g, '_') || '',
|
||||||
label: formData.label,
|
label: formData.label,
|
||||||
targetCells,
|
targetCells,
|
||||||
placeholder: formData.placeholder,
|
placeholder: formData.placeholder,
|
||||||
@@ -840,7 +820,7 @@ export const ElementConstructor: React.FC<ElementConstructorProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleUpdateElement = () => {
|
const handleUpdateElement = () => {
|
||||||
if (!editingElement || !formData.name || !formData.label) return
|
if (!editingElement || !formData.label) return
|
||||||
|
|
||||||
// Для элементов типа "standards", "calibration-conditions" и "button-group" не требуем targetCells, так как они генерируются автоматически или не нужны
|
// Для элементов типа "standards", "calibration-conditions" и "button-group" не требуем targetCells, так как они генерируются автоматически или не нужны
|
||||||
if (
|
if (
|
||||||
@@ -851,7 +831,15 @@ export const ElementConstructor: React.FC<ElementConstructorProps> = ({
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
onElementUpdate(editingElement.id, formData)
|
const updatedData = {
|
||||||
|
...formData,
|
||||||
|
name:
|
||||||
|
formData.label?.toLowerCase().replace(/\s+/g, '_') ||
|
||||||
|
formData.name ||
|
||||||
|
'',
|
||||||
|
}
|
||||||
|
|
||||||
|
onElementUpdate(editingElement.id, updatedData)
|
||||||
setEditingElement(null)
|
setEditingElement(null)
|
||||||
resetForm()
|
resetForm()
|
||||||
}
|
}
|
||||||
@@ -920,7 +908,6 @@ export const ElementConstructor: React.FC<ElementConstructorProps> = ({
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleAddElement}
|
onClick={handleAddElement}
|
||||||
disabled={
|
disabled={
|
||||||
!formData.name ||
|
|
||||||
!formData.label ||
|
!formData.label ||
|
||||||
(formData.type !== 'standards' &&
|
(formData.type !== 'standards' &&
|
||||||
formData.type !== 'calibration-conditions' &&
|
formData.type !== 'calibration-conditions' &&
|
||||||
|
|||||||
Reference in New Issue
Block a user