import { getAvailableElementTypes, getElementDefinition, } from '@/entitiy/element/model/interface' import { useToast } from '@/lib/hooks/useToast' import { generateDefaultLayout } from '@/lib/utils' import { CellTarget, ElementType, FormLayoutSettings, Template, TemplateElement, } from '@/type/template' import { AlertTriangle, Eye, HelpCircle, Plus, Settings2, Tag, Target, Trash2, Type, } from 'lucide-react' import React, { useState } from 'react' import { Button } from '../ui/button' import { Card, CardContent, CardHeader, CardTitle } from '../ui/card' import { Checkbox } from '../ui/checkbox' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '../ui/dialog' import { Input } from '../ui/input' import { Label } from '../ui/label' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '../ui/select' import { Separator } from '../ui/separator' import { ElementFormulaBar } from './ElementFormulaBar' import { VisualLayoutEditor } from './VisualLayoutEditor' interface ElementFormProps { formData: Partial setFormData: React.Dispatch>> } const CellTargetEditor: React.FC<{ targets: CellTarget[] onChange: (targets: CellTarget[]) => void }> = ({ targets, onChange }) => { const { warning } = useToast() const handleAddTarget = () => { onChange([...targets, { sheet: 'L', cell: '' }]) } const handleUpdateTarget = ( index: number, field: keyof CellTarget, value: string ) => { const updatedTargets = targets.map((target, i) => i === index ? { ...target, [field]: value } : target ) onChange(updatedTargets) } const handleRemoveTarget = (index: number) => { onChange(targets.filter((_, i) => i !== index)) } const handleCellInputChange = (index: number, value: string) => { // Проверяем на наличие кириллицы const hasCyrillic = /[а-яё]/i.test(value) if (hasCyrillic) { warning('Кириллические символы не разрешены в адресе ячейки', { title: 'Недопустимый символ', duration: 3000, }) return // Не обновляем значение } handleUpdateTarget(index, 'cell', value) } return (
Целевые ячейки
{targets.map((target, index) => (
{/* Кнопки выбора листа */}
{/* Поле ввода ячейки */} handleCellInputChange(index, e.target.value)} className="h-6 w-16 px-2 text-xs" /> {/* Кнопка удаления */}
))} {targets.length === 0 && (

Добавьте ячейки для связи с элементом

Например: A1, B5, C10

)}
{targets.length > 0 && (
Всего ячеек: {targets.length}
)}
) } // Live Preview компонент const ElementPreview: React.FC<{ element: Partial }> = ({ element, }) => { const elementDefinition = element.type ? getElementDefinition(element.type) : null return (
Предпросмотр элемента
{elementDefinition && elementDefinition.Preview ? ( ) : (
{element.label && ( )}
{element.type ? `Элемент типа "${element.type}" не найден` : 'Выберите тип элемента'}
)}
) } const ElementForm: React.FC = ({ formData, setFormData }) => { const elementDefinition = formData.type ? getElementDefinition(formData.type) : null return (
{/* Настройки элемента */}
{/* Основные настройки */}
Основные настройки
setFormData(prev => ({ ...prev, label: e.target.value })) } className="h-9" />
setFormData(prev => ({ ...prev, placeholder: e.target.value, })) } className="h-9" />
setFormData(prev => ({ ...prev, required: !!checked })) } />
{/* Целевые ячейки */} setFormData(prev => ({ ...prev, targetCells: targets })) } /> {/* Специфичный редактор элемента */} {elementDefinition && elementDefinition.Editor && (
Дополнительные настройки
setFormData(prev => ({ ...prev, ...newConfig })) } />
)}
{/* Предпросмотр */}
) } interface CanvasSettingsProps { settings: FormLayoutSettings onChange: (settings: FormLayoutSettings) => void } const CanvasSettings: React.FC = ({ settings, onChange, }) => { return (
onChange({ ...settings, showGrid: !!checked }) } />
) } interface ElementConstructorProps { template: Template onElementAdd: (element: TemplateElement) => void // Принимаем частичное обновление элемента, чтобы корректно обновлять только изменённые поля onElementUpdate: ( elementId: string, updates: Partial ) => void onElementDelete: (elementId: string) => void disabled?: boolean // Пропсы для сохранения hasUnsavedChanges?: boolean isSaving?: boolean lastSaveError?: string | null onManualSave?: () => Promise onClearError?: () => void // Пропсы для настроек макета onLayoutSettingsChange?: (settings: FormLayoutSettings) => void } export const ElementConstructor: React.FC = ({ template, onElementAdd, onElementUpdate, onElementDelete, disabled = false, hasUnsavedChanges = false, isSaving = false, lastSaveError = null, onManualSave, onClearError, onLayoutSettingsChange, }) => { const elements = template.elements const layoutSettings = template.layoutSettings || { gridSize: 20, showGrid: true, } const [isAddDialogOpen, setIsAddDialogOpen] = useState(false) const [editingElement, setEditingElement] = useState( null ) // Функция для создания дефолтной конфигурации const createDefaultFormData = (type: ElementType = 'text') => { const elementDefinition = getElementDefinition(type) const baseData = { type, name: '', label: '', targetCells: [], placeholder: '', required: false, options: [], } // Применяем defaultConfig если он есть if (elementDefinition && elementDefinition.defaultConfig) { Object.assign(baseData, elementDefinition.defaultConfig) } return baseData } const [formData, setFormData] = useState>( createDefaultFormData() ) const resetForm = () => { setFormData(createDefaultFormData()) } const handleAddElement = () => { if (!formData.label) { return } // Получаем определение элемента const elementDefinition = getElementDefinition(formData.type as ElementType) if (!elementDefinition) { console.error(`Element definition for ${formData.type} not found`) return } // Генерируем layout для нового элемента const defaultLayout = generateDefaultLayout(elements.length) // Используем targetCells из формы или генерируем через mapToCells let targetCells = formData.targetCells || [] if (!targetCells.length) { targetCells = elementDefinition.mapToCells(formData as any) || [] } const newElement: TemplateElement = { ...formData, // Копируем все поля из formData, включая специфичные для элемента id: Date.now().toString(), type: formData.type as ElementType, name: formData.label || '', label: formData.label, targetCells, placeholder: formData.placeholder, required: formData.required, options: formData.options, order: elements.length, layout: defaultLayout, } as TemplateElement onElementAdd(newElement) resetForm() setIsAddDialogOpen(false) } const handleEditElement = (element: TemplateElement) => { setEditingElement(element) setFormData(element) } const handleUpdateElement = () => { if (!editingElement || !formData.label) return // Получаем определение элемента const elementDefinition = getElementDefinition( editingElement.type as ElementType ) if (!elementDefinition) { console.error(`Element definition for ${editingElement.type} not found`) return } // Используем targetCells из формы или генерируем через mapToCells let targetCells = formData.targetCells || [] if (!targetCells.length) { targetCells = elementDefinition.mapToCells(formData as any) || [] } const updatedData = { ...formData, name: formData.label || formData.name || '', targetCells, } onElementUpdate(editingElement.id, updatedData) setEditingElement(null) resetForm() } const handleCancelEdit = () => { setEditingElement(null) resetForm() } const handleDeleteAndSelect = (elementId: string) => { onElementDelete(elementId) setEditingElement(null) resetForm() } const handleElementCopy = (element: Omit) => { const newElement: TemplateElement = { ...element, id: Date.now().toString(), order: elements.length, } onElementAdd(newElement) } return (
setIsAddDialogOpen(true)} disabled={disabled} hasUnsavedChanges={hasUnsavedChanges} isSaving={isSaving} lastSaveError={lastSaveError} onManualSave={onManualSave} onClearError={onClearError} showGrid={layoutSettings.showGrid} onToggleGrid={ onLayoutSettingsChange ? () => { const newSettings = { ...layoutSettings, showGrid: !layoutSettings.showGrid, } onLayoutSettingsChange(newSettings) } : undefined } />
{})} onElementEdit={handleEditElement} onElementAdd={handleElementCopy} />
{/* Диалог добавления элемента */} Добавить новый элемент Создайте и настройте новый элемент формы. Вы можете увидеть предпросмотр справа.
{!formData.label && (
Заполните обязательные поля
)}
{/* Диалог редактирования элемента */} {editingElement && ( handleCancelEdit()}> Редактировать элемент Измените параметры элемента " {editingElement.label || editingElement.name}".
{!formData.label && (
Заполните обязательные поля
)}
)}
) }