бета отправка измнений
This commit is contained in:
@@ -22,6 +22,7 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
||||
templateData = {},
|
||||
mergedCells = [],
|
||||
templateId,
|
||||
onEngineReady,
|
||||
}) => {
|
||||
const { revision, updateRevision, setTemplateData, ...multiSheetEngine } =
|
||||
useMultiSheetEngine({
|
||||
@@ -32,6 +33,17 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
||||
initialData: templateData || {},
|
||||
})
|
||||
|
||||
// Передаем методы движка родительскому компоненту
|
||||
useEffect(() => {
|
||||
if (onEngineReady) {
|
||||
onEngineReady({
|
||||
...multiSheetEngine,
|
||||
updateRevision,
|
||||
})
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [onEngineReady])
|
||||
|
||||
const [activeSheet, setActiveSheet] = useState<SheetType>('report')
|
||||
const [selectedCell, setSelectedCell] = useState<{
|
||||
row: number
|
||||
@@ -684,7 +696,6 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
||||
|
||||
// Обновляем baseline, включая уже сохранённые изменения пользователя,
|
||||
// чтобы их не сохранять повторно
|
||||
setTemplateData(mergedData)
|
||||
} else if (Object.keys(finalBaseData).length > 0) {
|
||||
console.log(
|
||||
'📋 Используем только базовые данные (нет пользовательских изменений)'
|
||||
|
||||
@@ -29,6 +29,7 @@ export interface DualSpreadsheetProps {
|
||||
templateData?: Record<string, Record<string, any>>
|
||||
mergedCells?: MergedCell[] // Добавляем поддержку объединенных ячеек
|
||||
templateId?: string // Добавляем ID шаблона для сохранения
|
||||
onEngineReady?: (engine: any) => void // Коллбэк, который отдаёт родителю методы движка
|
||||
}
|
||||
|
||||
export interface CellProps {
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { useTemplateContext } from '@/contexts/TemplateContext'
|
||||
import { getElementDefinition } from '@/lib/element-registry'
|
||||
import { getLatestFileForTemplate } from '@/services/fileApiSevice'
|
||||
import { Template, TemplateElement } from '@/types/template'
|
||||
import {
|
||||
ArrowLeft,
|
||||
@@ -26,7 +27,7 @@ import {
|
||||
Settings,
|
||||
Wrench,
|
||||
} from 'lucide-react'
|
||||
import { FC, useEffect, useMemo, useState } from 'react'
|
||||
import { FC, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
|
||||
// Моковые данные для эталонов (такие же как в StandardsSelector)
|
||||
@@ -368,15 +369,54 @@ const FormElement: FC<FormElementProps> = ({ element, value, onChange }) => {
|
||||
const ProtocolForm: FC<ProtocolFormProps> = ({ template, onSave, onBack }) => {
|
||||
const [formData, setFormData] = useState<Record<string, any>>({})
|
||||
const [showSpreadsheet, setShowSpreadsheet] = useState(false)
|
||||
const engineRef = useRef<any>(null)
|
||||
|
||||
// 👉 Хелперы для конвертации адреса ячейки
|
||||
const labelToColumn = (label: string) => {
|
||||
let col = 0
|
||||
for (let i = 0; i < label.length; i++) {
|
||||
col = col * 26 + (label.charCodeAt(i) - 64)
|
||||
}
|
||||
return col - 1 // zero-based
|
||||
}
|
||||
|
||||
const parseCellAddress = (addr: string) => {
|
||||
const match = addr.match(/([A-Z]+)(\d+)/i)
|
||||
if (!match) return { row: 0, col: 0 }
|
||||
const [, colLabel, rowNum] = match
|
||||
return {
|
||||
row: parseInt(rowNum, 10) - 1,
|
||||
col: labelToColumn(colLabel),
|
||||
}
|
||||
}
|
||||
|
||||
const handleFieldChange = (elementId: string, value: any) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[elementId]: value,
|
||||
}))
|
||||
|
||||
// Прямо обновляем движок, чтобы формулы пересчитались
|
||||
if (engineRef.current) {
|
||||
const element = template.elements.find(el => el.id === elementId)
|
||||
if (element && element.targetCells) {
|
||||
element.targetCells.forEach(tc => {
|
||||
const { row, col } = parseCellAddress(tc.cell)
|
||||
const sheetName =
|
||||
tc.sheet === 'R' || tc.sheet === 'Calculations' ? 'R' : 'L'
|
||||
engineRef.current.setCellValueWithoutRecalc(
|
||||
sheetName,
|
||||
row,
|
||||
col,
|
||||
value
|
||||
)
|
||||
})
|
||||
engineRef.current.debouncedRecalc()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
const handleSave = async () => {
|
||||
// Ищем элемент с названием протокола (должен иметь специальный тип или название)
|
||||
const protocolNameElement = template.elements.find(
|
||||
el => el.name === 'protocolName' || el.label === 'Название протокола'
|
||||
@@ -392,6 +432,106 @@ const ProtocolForm: FC<ProtocolFormProps> = ({ template, onSave, onBack }) => {
|
||||
createdAt: new Date(),
|
||||
}
|
||||
onSave(data)
|
||||
|
||||
console.log(
|
||||
'[DBG] template elements',
|
||||
template.elements.map(e => ({
|
||||
id: e.id,
|
||||
label: e.label,
|
||||
target: e.targetCells,
|
||||
type: e.type,
|
||||
}))
|
||||
)
|
||||
|
||||
try {
|
||||
if (!engineRef.current) {
|
||||
alert('Движок ещё не инициализирован')
|
||||
return
|
||||
}
|
||||
|
||||
// 1. Записываем ВСЕ значения формы в движок (поверх текущих)
|
||||
template.elements.forEach(el => {
|
||||
let value = formData[el.id]
|
||||
if (value === undefined || value === null) value = ''
|
||||
|
||||
let cells =
|
||||
el.targetCells && el.targetCells.length > 0
|
||||
? el.targetCells
|
||||
: undefined
|
||||
if ((!cells || cells.length === 0) && el.type) {
|
||||
const def = getElementDefinition(el.type as any)
|
||||
if (def?.mapToCells) cells = def.mapToCells(el as any)
|
||||
}
|
||||
|
||||
cells?.forEach(tc => {
|
||||
const { row, col } = parseCellAddress(tc.cell)
|
||||
const sheetName =
|
||||
tc.sheet === 'R' || tc.sheet === 'Calculations' ? 'R' : 'L'
|
||||
console.log(
|
||||
'[DBG] write to',
|
||||
tc.cell,
|
||||
'sheet',
|
||||
sheetName,
|
||||
'value',
|
||||
value
|
||||
)
|
||||
engineRef.current.setCellValueWithoutRecalc(
|
||||
sheetName,
|
||||
row,
|
||||
col,
|
||||
value
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
// 2. Пересчитываем формулы
|
||||
engineRef.current.recalculate()
|
||||
|
||||
// 3. Получаем изменения только левого листа относительно базового файла
|
||||
const allModified = engineRef.current?.getAllModifiedCells() || {}
|
||||
// Преобразуем в конечные (вычисленные) значения
|
||||
const cellsToUpdate: Record<string, any> = {}
|
||||
const modifiedLeft = allModified['L'] || {}
|
||||
Object.keys(modifiedLeft).forEach(cellAddress => {
|
||||
const { row, col } = parseCellAddress(cellAddress)
|
||||
cellsToUpdate[cellAddress] = engineRef.current.getCellValue(
|
||||
'L',
|
||||
row,
|
||||
col
|
||||
)
|
||||
})
|
||||
|
||||
console.log('[DBG] Final cells_to_update', cellsToUpdate)
|
||||
|
||||
// Получаем последний файл для шаблона
|
||||
const latestFile = await getLatestFileForTemplate(template.id)
|
||||
if (!latestFile) {
|
||||
alert('Не найден исходный файл Excel для шаблона')
|
||||
return
|
||||
}
|
||||
|
||||
const body = {
|
||||
cells_to_update: cellsToUpdate,
|
||||
file_id: latestFile.id,
|
||||
template_id: template.id,
|
||||
}
|
||||
|
||||
console.log('[DBG] POST body', body)
|
||||
|
||||
const resp = await fetch('/api/protocols/', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
|
||||
|
||||
const result = await resp.json()
|
||||
console.log('✅ Протокол создан, ID:', result.protocol_id)
|
||||
} catch (err) {
|
||||
console.error('Ошибка при создании протокола', err)
|
||||
alert('Ошибка при создании протокола')
|
||||
}
|
||||
}
|
||||
|
||||
const handleExport = () => {
|
||||
@@ -467,20 +607,22 @@ const ProtocolForm: FC<ProtocolFormProps> = ({ template, onSave, onBack }) => {
|
||||
|
||||
{/* Основное содержимое */}
|
||||
<div className="flex-1 overflow-hidden">
|
||||
{showSpreadsheet ? (
|
||||
/* Режим таблиц */
|
||||
<div className="h-full">
|
||||
{/* Всегда монтируем DualSpreadsheet, скрываем/показываем через hidden */}
|
||||
<div className={`${showSpreadsheet ? 'block' : 'hidden'} h-full`}>
|
||||
<DualSpreadsheet
|
||||
templateData={
|
||||
template.excelData ? { report: template.excelData } : {}
|
||||
}
|
||||
mergedCells={template.mergedCells || []}
|
||||
templateId={template.id}
|
||||
onEngineReady={engine => (engineRef.current = engine)}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
/* Режим формы */
|
||||
<div className="h-full overflow-auto">
|
||||
|
||||
{/* Форма */}
|
||||
<div
|
||||
className={`${showSpreadsheet ? 'hidden' : 'block'} h-full overflow-auto`}
|
||||
>
|
||||
<div
|
||||
className="relative mx-auto my-8"
|
||||
style={{ width: canvasSize.width, height: canvasSize.height }}
|
||||
@@ -534,7 +676,6 @@ const ProtocolForm: FC<ProtocolFormProps> = ({ template, onSave, onBack }) => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user