diff --git a/src/component/BasicElements/NumberElement.tsx b/src/component/BasicElements/NumberElement.tsx deleted file mode 100644 index e9b434d..0000000 --- a/src/component/BasicElements/NumberElement.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Input } from '@/component/ui/input' -import { ElementDefinition } from '@/entitiy/element/model/interface' -import { Hash } from 'lucide-react' - -interface NumberConfig { - placeholder?: string - required?: boolean - targetCells: any[] -} - -export const numberDefinition: ElementDefinition = { - type: 'number', - label: 'Число', - icon: , - version: 1, - defaultConfig: { - placeholder: '', - required: false, - targetCells: [], - }, - mapToCells: config => config.targetCells || [], - mapToCellValues: (config, value) => { - const cells = config.targetCells || [] - return cells.map(target => ({ target, value: value || 0 })) - }, - Editor: () => ( -
- Дополнительные настройки отсутствуют -
- ), - Preview: ({ config }) => ( -
-
Предпросмотр
-
- -
-
- ), - Render: ({ config, value, onChange }) => ( - onChange?.(parseFloat(e.target.value) || 0)} - required={config.required} - /> - ), -} diff --git a/src/component/BasicElements/index.ts b/src/component/BasicElements/index.ts deleted file mode 100644 index d9e0bb6..0000000 --- a/src/component/BasicElements/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { textDefinition } from '@/entitiy/element/model/implementations/TextElement' -export { buttonGroupDefinition } from '../../entitiy/element/model/implementations/ButtonGroup' -export { dateDefinition } from '../../entitiy/element/model/implementations/DateElement' -export { numberDefinition } from './NumberElement' -export { selectDefinition } from './SelectElement' diff --git a/src/entitiy/element/model/implementations/DateElement.tsx b/src/entitiy/element/model/implementations/DateElement.tsx deleted file mode 100644 index d63fa99..0000000 --- a/src/entitiy/element/model/implementations/DateElement.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Input } from '@/component/ui/input' -import { ElementDefinition } from '@/entitiy/element/model/interface' -import { ExtraSettingsBadge } from '@/entitiy/element/ui/extraSettingsBadge' -import { Calendar } from 'lucide-react' - -interface DateConfig { - required?: boolean - targetCells: any[] -} - -export const dateDefinition: ElementDefinition = { - type: 'date', - label: 'Дата', - icon: , - version: 1, - defaultConfig: { - required: false, - targetCells: [], - }, - mapToCells: config => config.targetCells || [], - mapToCellValues: (config, value) => { - const cells = config.targetCells || [] - return cells.map(target => ({ target, value: value || '' })) - }, - Editor: () => , - Preview: () => ( -
- -
- ), - Render: ({ config, value, onChange }) => ( - onChange?.(e.target.value)} - required={config.required} - /> - ), -} diff --git a/src/component/BasicElements/SelectElement.tsx b/src/entitiy/element/model/implementations/SelectElement.tsx similarity index 91% rename from src/component/BasicElements/SelectElement.tsx rename to src/entitiy/element/model/implementations/SelectElement.tsx index e45c2cd..b391ac4 100644 --- a/src/component/BasicElements/SelectElement.tsx +++ b/src/entitiy/element/model/implementations/SelectElement.tsx @@ -31,6 +31,26 @@ export const selectDefinition: ElementDefinition = { options: [], targetCells: [], }, + getInitialValue: config => { + // Проверяем доступность localStorage (может не быть в SSR) + if (typeof window === 'undefined' || !window.localStorage) { + return '' + } + + const elementId = (config as any).id || config.placeholder || 'default' + const storageKey = `select_${elementId}` + const value = localStorage.getItem(storageKey) || '' + + // Добавляем отладочную информацию + console.log('SelectElement getInitialValue:', { + elementId, + storageKey, + value, + config, + }) + + return value + }, mapToCells: config => config.targetCells || [], mapToCellValues: (config, value) => { const cells = config.targetCells || [] diff --git a/src/component/VerificationConditionsElement/VerificationConditionsEditor.tsx b/src/entitiy/element/model/implementations/VerificationConditionsElement/VerificationConditionsEditor.tsx similarity index 84% rename from src/component/VerificationConditionsElement/VerificationConditionsEditor.tsx rename to src/entitiy/element/model/implementations/VerificationConditionsElement/VerificationConditionsEditor.tsx index 4439027..c134f59 100644 --- a/src/component/VerificationConditionsElement/VerificationConditionsEditor.tsx +++ b/src/entitiy/element/model/implementations/VerificationConditionsElement/VerificationConditionsEditor.tsx @@ -8,7 +8,7 @@ import { SelectValue, } from '@/component/ui/select' import { CellTarget } from '@/type/template' -import { Building2 } from 'lucide-react' +import { Building2, Calendar } from 'lucide-react' import { useLaboratories, useLaboratoryConditions } from './hooks' import { ConditionMapping, VerificationConditionsConfig } from './types' @@ -33,7 +33,7 @@ export function VerificationConditionsEditor({ return config.targetCells || [] } - // Обновить маппинг для конкретного условия + // Обновить маппинг для конкретного условия (включая дату поверки) const updateConditionMapping = ( conditionId: string, conditionName: string, @@ -94,6 +94,24 @@ export function VerificationConditionsEditor({ const targetCells = getAvailableTargetCells() + // Создаем список всех условий включая дату поверки + const allConditions = [ + // Дата поверки как первый элемент + { + id: 'verification_date', + name: 'Дата поверки', + unit: '', + isDate: true, + }, + // Условия из лаборатории + ...(labData?.conditionTypes || []).map(ct => ({ + id: ct.id, + name: ct.name, + unit: ct.unit, + isDate: false, + })), + ] + return (
{/* Компактный выбор лаборатории */} @@ -128,30 +146,38 @@ export function VerificationConditionsEditor({
- {/* Маппинг условий */} - {labData && targetCells.length > 0 && ( + {/* Маппинг условий (включая дату поверки) */} + {targetCells.length > 0 && (
- {labData.conditionTypes.map(conditionType => { - const selectedCell = getSelectedTargetCell(conditionType.id) - const occupiedCells = getOccupiedTargetCells(conditionType.id) + {allConditions.map(condition => { + const selectedCell = getSelectedTargetCell(condition.id) + const occupiedCells = getOccupiedTargetCells(condition.id) return (
{/* Информация об условии */}
+ {condition.isDate && ( + + )} - {conditionType.name} + {condition.name} - - {conditionType.unit} - + {condition.unit && ( + + {condition.unit} + + )}
{/* Компактный селектор ячейки */} @@ -159,8 +185,8 @@ export function VerificationConditionsEditor({ value={selectedCell || 'UNSELECTED'} onValueChange={value => updateConditionMapping( - conditionType.id, - conditionType.name, + condition.id, + condition.name, value === 'UNSELECTED' ? undefined : value ) } @@ -186,7 +212,7 @@ export function VerificationConditionsEditor({ ) })} - {labData.conditionTypes.length === 0 && ( + {allConditions.length === 1 && (

У выбранной лаборатории нет настроенных типов условий diff --git a/src/component/VerificationConditionsElement/VerificationConditionsPreview.tsx b/src/entitiy/element/model/implementations/VerificationConditionsElement/VerificationConditionsPreview.tsx similarity index 100% rename from src/component/VerificationConditionsElement/VerificationConditionsPreview.tsx rename to src/entitiy/element/model/implementations/VerificationConditionsElement/VerificationConditionsPreview.tsx diff --git a/src/component/VerificationConditionsElement/VerificationConditionsRender.tsx b/src/entitiy/element/model/implementations/VerificationConditionsElement/VerificationConditionsRender.tsx similarity index 94% rename from src/component/VerificationConditionsElement/VerificationConditionsRender.tsx rename to src/entitiy/element/model/implementations/VerificationConditionsElement/VerificationConditionsRender.tsx index 311da0c..a20cc69 100644 --- a/src/component/VerificationConditionsElement/VerificationConditionsRender.tsx +++ b/src/entitiy/element/model/implementations/VerificationConditionsElement/VerificationConditionsRender.tsx @@ -42,7 +42,7 @@ export function VerificationConditionsRender({ onSave, }: VerificationConditionsRenderProps) { const [selectedDate, setSelectedDate] = useState( - config.selectedDate || new Date().toISOString().split('T')[0] + value?.date || config.selectedDate || new Date().toISOString().split('T')[0] ) const [editDialogOpen, setEditDialogOpen] = useState(false) const [editingConditions, setEditingConditions] = useState< @@ -79,8 +79,34 @@ export function VerificationConditionsRender({ } }, [dailyCondition, hasConditions, onChange, selectedDate, value?.conditions]) + // Синхронизация selectedDate с внешним value.date + useEffect(() => { + if (value?.date && value.date !== selectedDate) { + setSelectedDate(value.date) + } + }, [value?.date, selectedDate]) + + // Инициализация значения если оно не установлено + useEffect(() => { + if (!value && onChange) { + onChange({ + date: selectedDate, + conditions: {}, + }) + } + }, [value, onChange, selectedDate]) + const handleDateChange = (event: React.ChangeEvent) => { - setSelectedDate(event.target.value) + const newDate = event.target.value + setSelectedDate(newDate) + + // Обновляем значение даты в value + if (onChange) { + onChange({ + date: newDate, + conditions: value?.conditions || {}, + }) + } } const handleOpenEditDialog = (isCreating = false) => { diff --git a/src/component/VerificationConditionsElement/api.ts b/src/entitiy/element/model/implementations/VerificationConditionsElement/api.ts similarity index 100% rename from src/component/VerificationConditionsElement/api.ts rename to src/entitiy/element/model/implementations/VerificationConditionsElement/api.ts diff --git a/src/component/VerificationConditionsElement/definition.tsx b/src/entitiy/element/model/implementations/VerificationConditionsElement/definition.tsx similarity index 62% rename from src/component/VerificationConditionsElement/definition.tsx rename to src/entitiy/element/model/implementations/VerificationConditionsElement/definition.tsx index bd78d5a..03c11fd 100644 --- a/src/component/VerificationConditionsElement/definition.tsx +++ b/src/entitiy/element/model/implementations/VerificationConditionsElement/definition.tsx @@ -9,6 +9,40 @@ import { VerificationConditionsEditor } from './VerificationConditionsEditor' import { VerificationConditionsPreview } from './VerificationConditionsPreview' import { VerificationConditionsRender } from './VerificationConditionsRender' +// Форматирование даты как в функции ДАТАПРОТОКОЛА +const formatVerificationDate = (dateStr: string): string => { + try { + const months = [ + '', + 'января', + 'февраля', + 'марта', + 'апреля', + 'мая', + 'июня', + 'июля', + 'августа', + 'сентября', + 'октября', + 'ноября', + 'декабря', + ] + + const dateObj = new Date(dateStr) + if (isNaN(dateObj.getTime())) { + return 'Неверный формат даты' + } + + const day = dateObj.getDate().toString().padStart(2, '0') + const month = months[dateObj.getMonth() + 1] + const year = dateObj.getFullYear() + + return `${day} ${month} ${year}` + } catch (e) { + return 'Неверный формат даты' + } +} + export const verificationConditionsElementDefinition: ElementDefinition< VerificationConditionsConfig & { targetCells?: CellTarget[] }, VerificationConditionsValue @@ -56,28 +90,40 @@ export const verificationConditionsElementDefinition: ElementDefinition< config, value: VerificationConditionsValue ): Array<{ target: CellTarget; value: any }> => { - if (!config.conditionMappings || !config.targetCells || !value.conditions) { + if (!config.conditionMappings || !config.targetCells) { return [] } return config.conditionMappings - .filter(mapping => { - const conditionValue = value.conditions[mapping.conditionKey] - // Записываем только непустые значения - return ( - conditionValue !== undefined && - conditionValue !== null && - String(conditionValue).trim() !== '' - ) - }) .map(mapping => { const targetCell = config.targetCells?.[mapping.cellIndex] if (!targetCell) return null - return { - target: targetCell, - value: value.conditions[mapping.conditionKey], + // Обработка даты поверки + if (mapping.conditionKey === 'verification_date') { + if (value.date) { + return { + target: targetCell, + value: formatVerificationDate(value.date), + } + } + return null } + + // Обработка остальных условий + const conditionValue = value.conditions?.[mapping.conditionKey] + if ( + conditionValue !== undefined && + conditionValue !== null && + String(conditionValue).trim() !== '' + ) { + return { + target: targetCell, + value: conditionValue, + } + } + + return null }) .filter(Boolean) as Array<{ target: CellTarget; value: any }> }, diff --git a/src/component/VerificationConditionsElement/hooks.ts b/src/entitiy/element/model/implementations/VerificationConditionsElement/hooks.ts similarity index 100% rename from src/component/VerificationConditionsElement/hooks.ts rename to src/entitiy/element/model/implementations/VerificationConditionsElement/hooks.ts diff --git a/src/component/VerificationConditionsElement/index.ts b/src/entitiy/element/model/implementations/VerificationConditionsElement/index.ts similarity index 100% rename from src/component/VerificationConditionsElement/index.ts rename to src/entitiy/element/model/implementations/VerificationConditionsElement/index.ts diff --git a/src/component/VerificationConditionsElement/mockData.ts b/src/entitiy/element/model/implementations/VerificationConditionsElement/mockData.ts similarity index 100% rename from src/component/VerificationConditionsElement/mockData.ts rename to src/entitiy/element/model/implementations/VerificationConditionsElement/mockData.ts diff --git a/src/component/VerificationConditionsElement/types.ts b/src/entitiy/element/model/implementations/VerificationConditionsElement/types.ts similarity index 96% rename from src/component/VerificationConditionsElement/types.ts rename to src/entitiy/element/model/implementations/VerificationConditionsElement/types.ts index eb2f71f..f0a4234 100644 --- a/src/component/VerificationConditionsElement/types.ts +++ b/src/entitiy/element/model/implementations/VerificationConditionsElement/types.ts @@ -13,7 +13,7 @@ export interface VerificationConditionsConfig { selectedLaboratoryId?: string // Выбранная дата selectedDate?: string - // Маппинг условий в ячейки + // Маппинг условий в ячейки (включая дату поверки) conditionMappings?: ConditionMapping[] // Показывать ли единицы измерения showUnits?: boolean diff --git a/src/entitiy/element/model/interface.ts b/src/entitiy/element/model/interface.ts index 65b2459..3d7d931 100644 --- a/src/entitiy/element/model/interface.ts +++ b/src/entitiy/element/model/interface.ts @@ -1,10 +1,8 @@ -import { textDefinition } from '@/component/BasicElements' -import { numberDefinition } from '@/component/BasicElements/NumberElement' -import { selectDefinition } from '@/component/BasicElements/SelectElement' -import { verificationConditionsElementDefinition as verificationConditionsDefinition } from '@/component/VerificationConditionsElement/definition' import { buttonGroupDefinition } from '@/entitiy/element/model/implementations/ButtonGroup' -import { dateDefinition } from '@/entitiy/element/model/implementations/DateElement' +import { selectDefinition } from '@/entitiy/element/model/implementations/SelectElement' import { standardsDefinition } from '@/entitiy/element/model/implementations/StandardsElement/definition' +import { textDefinition } from '@/entitiy/element/model/implementations/TextElement' +import { verificationConditionsElementDefinition as verificationConditionsDefinition } from '@/entitiy/element/model/implementations/VerificationConditionsElement/definition' import { CellTarget } from '@/type/template' import { ReactNode } from 'react' @@ -64,8 +62,6 @@ export function getElementDefinition( export const elementDefinitions = { text: textDefinition, select: selectDefinition, - number: numberDefinition, - date: dateDefinition, standards: standardsDefinition, verification_conditions: verificationConditionsDefinition, button_group: buttonGroupDefinition, @@ -76,8 +72,6 @@ export type ElementType = keyof typeof elementDefinitions export function initializeElementRegistry() { registerElement('text', textDefinition) registerElement('select', selectDefinition) - registerElement('number', numberDefinition) - registerElement('date', dateDefinition) registerElement('button_group', buttonGroupDefinition) registerElement('standards', standardsDefinition) registerElement('verification_conditions', verificationConditionsDefinition) diff --git a/src/page/ProtocolCreation/Page.tsx b/src/page/ProtocolCreation/Page.tsx index 390313b..a9ca40d 100644 --- a/src/page/ProtocolCreation/Page.tsx +++ b/src/page/ProtocolCreation/Page.tsx @@ -16,61 +16,6 @@ interface ProtocolFormProps { onBack: () => void } -interface FormElementProps { - element: TemplateElement - value: any - onChange: (value: any) => void -} - -const FormElement: FC = ({ element, value, onChange }) => { - // Получаем определение элемента из реестра - const elementDefinition = getElementDefinition(element.type) - - // Если определение элемента не найдено, показываем ошибку - if (!elementDefinition) { - return ( -

- Элемент типа "{element.type}" не найден в реестре -
- ) - } - - const renderElement = () => ( - - ) - - return ( -
- {/* Показываем индикатор ячеек для всех элементов */} - {element.targetCells && element.targetCells.length > 0 && ( -
- -
-
Целевые ячейки:
- {element.targetCells.map((cell, i) => ( -
- {cell.sheet}!{cell.cell} - {cell.displayName && ( - - ({cell.displayName}) - - )} -
- ))} -
-
- )} - - {/* Рендер элемента через единый интерфейс */} - {renderElement()} -
- ) -} - const ProtocolForm: FC = ({ template, onSave, onBack }) => { const [formData, setFormData] = useState>({}) const [showSpreadsheet, setShowSpreadsheet] = useState(false) @@ -79,23 +24,22 @@ const ProtocolForm: FC = ({ template, onSave, onBack }) => { const toast = useToast() const navigate = useNavigate() - // Инициализация значений для специальных элементов + // Инициализация начальных значений формы (только один раз) useEffect(() => { if (!isInitialized && template.elements.length > 0) { const initialData: Record = {} - template.elements.forEach(element => { - // Получаем определение элемента const elementDefinition = getElementDefinition(element.type) if (elementDefinition?.getInitialValue) { - // Используем метод getInitialValue для получения начального значения const initialValue = elementDefinition.getInitialValue(element as any) - if (initialValue !== undefined) { + if (initialValue !== undefined && initialValue !== '') { initialData[element.id] = initialValue } } }) + console.log('Инициализация формы:', initialData) + if (Object.keys(initialData).length > 0) { setFormData(initialData) } @@ -104,26 +48,21 @@ const ProtocolForm: FC = ({ template, onSave, onBack }) => { } }, [template.elements, isInitialized]) - const handleFieldChange = (elementId: string, value: any) => { - setFormData(prev => ({ - ...prev, - [elementId]: value, - })) + // Универальная функция синхронизации всех данных формы с движком + const syncFormDataToEngine = () => { + if (!engineRef.current) return - // Прямо обновляем движок, чтобы формулы пересчитались - if (engineRef.current) { - const element = template.elements.find(el => el.id === elementId) - if (element) { - // Получаем определение элемента + console.log('Синхронизация всех данных формы с движком:', formData) + + template.elements.forEach(element => { + const value = formData[element.id] + if (value !== undefined) { const elementDefinition = getElementDefinition(element.type) if (elementDefinition) { - // Используем единый интерфейс для получения пар ячейка-значение const cellValues = elementDefinition.mapToCellValues( element as any, value ) - - // Записываем все значения в соответствующие ячейки cellValues.forEach(({ target, value: cellValue }) => { const { row, col } = cellAddressToCoordinates(target.cell) engineRef.current.setCellValueWithoutRecalc( @@ -133,24 +72,34 @@ const ProtocolForm: FC = ({ template, onSave, onBack }) => { cellValue ) }) - - engineRef.current.debouncedRecalc() } } + }) + + engineRef.current.debouncedRecalc() + } + + // Синхронизация с движком только когда он готов и есть данные + useEffect(() => { + if ( + isInitialized && + engineRef.current && + Object.keys(formData).length > 0 + ) { + syncFormDataToEngine() } + }, [isInitialized, formData, template.elements]) + + const handleFieldChange = (elementId: string, value: any) => { + // Обновляем только состояние формы + setFormData(prev => ({ + ...prev, + [elementId]: value, + })) } const handleSave = async () => { - // Ищем элемент с названием протокола (должен иметь специальный тип или название) - const protocolNameElement = template.elements.find( - el => el.name === 'protocolName' || el.label === 'Название протокола' - ) - const protocolName = protocolNameElement - ? formData[protocolNameElement.id] - : 'Новый протокол' - const data = { - protocolName, templateId: template.id, formData, createdAt: new Date(), @@ -163,43 +112,14 @@ const ProtocolForm: FC = ({ template, onSave, onBack }) => { return } - // 1. Записываем ВСЕ значения формы в движок (поверх текущих) - template.elements.forEach(el => { - let value = formData[el.id] - if (value === undefined || value === null) value = '' + // Убеждаемся что все данные записаны в движок перед сохранением + syncFormDataToEngine() - // Получаем определение элемента - const elementDefinition = getElementDefinition(el.type) - if (elementDefinition) { - // Используем единый интерфейс для получения пар ячейка-значение - const cellValues = elementDefinition.mapToCellValues(el as any, value) + // Небольшая задержка чтобы пересчёт завершился + await new Promise(resolve => setTimeout(resolve, 100)) - // Записываем все значения в соответствующие ячейки - cellValues.forEach(({ target, value: cellValue }) => { - const { row, col } = cellAddressToCoordinates(target.cell) - if (target.sheet === 'Calculations') { - toast.warning('Обнаружен лист Calculations') - } - const sheetName = - target.sheet === 'R' || target.sheet === 'Calculations' - ? 'R' - : 'L' - engineRef.current.setCellValueWithoutRecalc( - sheetName, - row, - col, - cellValue - ) - }) - } - }) - - // 2. Пересчитываем формулы - engineRef.current.recalculate() - - // 3. Получаем изменения только левого листа относительно базового файла + // Получаем изменения только левого листа относительно базового файла const allModified = engineRef.current?.getAllModifiedCells() || {} - // Преобразуем в конечные (вычисленные) значения const cellsToUpdate: Record = {} const modifiedLeft = allModified['L'] || {} Object.keys(modifiedLeft).forEach(cellAddress => { @@ -223,6 +143,7 @@ const ProtocolForm: FC = ({ template, onSave, onBack }) => { file_id: latestFile.id, template_id: template.id, } + console.log('body', body) const resp = await fetch('/api/protocols/', { method: 'POST', @@ -240,7 +161,6 @@ const ProtocolForm: FC = ({ template, onSave, onBack }) => { // Получаем значение из ячейки R!A1 для имени файла const filenameFromCell = engineRef.current.getCellValue('R', 0, 0) // R!A1 - console.log(filenameFromCell) let filename = 'Измените R!A1.xlsx' if ( @@ -248,16 +168,12 @@ const ProtocolForm: FC = ({ template, onSave, onBack }) => { typeof filenameFromCell === 'string' && filenameFromCell.trim() ) { - // Очищаем имя файла от недопустимых символов const cleanName = filenameFromCell.trim().replace(/[<>:"/\\|?*]/g, '_') filename = `${cleanName}.xlsx` } - // Если сервер передал имя файла в заголовке Content-Disposition, игнорируем его - // и используем строго значение из R!A1 const contentDisposition = resp.headers.get('content-disposition') if (contentDisposition) { - // Оставляем возможность переопределения только если R!A1 пуста if (!filenameFromCell || !filenameFromCell.toString().trim()) { const matches = contentDisposition.match( /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/ @@ -373,6 +289,7 @@ const ProtocolForm: FC = ({ template, onSave, onBack }) => { templateId={template.id} onEngineReady={engine => { engineRef.current = engine + // Синхронизация произойдёт автоматически через useEffect }} enableAutoSave={false} /> @@ -458,14 +375,6 @@ export const ProtocolCreation: FC = () => { } }, [templateId, templates]) - const handleProtocolSave = (data: Record) => { - toast.success(`Протокол "${data.protocolName}" сохранен!`) - } - - const handleBack = () => { - navigate('/templates') - } - if (!selectedTemplate) { return (
@@ -488,8 +397,62 @@ export const ProtocolCreation: FC = () => { return ( { + toast.info(`Подождите, Excel файл создаётся...`) + }} + onBack={() => { + navigate('/templates') + }} /> ) } + +interface FormElementProps { + element: TemplateElement + value: any + onChange: (value: any) => void +} + +const FormElement: FC = ({ element, value, onChange }) => { + const elementDefinition = getElementDefinition(element.type) + if (!elementDefinition) { + return ( +
+ Элемент типа "{element.type}" не найден в реестре +
+ ) + } + + const renderElement = () => ( + + ) + + return ( +
+ {/* Показываем индикатор ячеек для всех элементов */} + {element.targetCells && element.targetCells.length > 0 && ( +
+ +
+
Целевые ячейки:
+ {element.targetCells.map((cell, i) => ( +
+ {cell.sheet}!{cell.cell} + {cell.displayName && ( + + ({cell.displayName}) + + )} +
+ ))} +
+
+ )} + {renderElement()} +
+ ) +}