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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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