редактор элементов изменен

This commit is contained in:
2025-07-18 16:36:31 +03:00
parent 78643f94b7
commit 30a5526de2
9 changed files with 270 additions and 400 deletions

View File

@@ -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">

View File

@@ -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>
), ),
}; }

View File

@@ -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}
/> />
), ),
}; }

View File

@@ -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}
/> />
), ),
}; }

View File

@@ -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>
), ),
}; }

View File

@@ -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>
), ),
}; }

View File

@@ -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}
/> />
), ),
}; }

View File

@@ -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"
/> />
), ),
}; }

View File

@@ -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' &&