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