diff --git a/src/app/App.tsx b/src/app/App.tsx index f624d3a..8d6e11d 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,10 +1,10 @@ -import { FC } from "react"; -import { Navigate, Route, Routes } from "react-router-dom"; -import { TemplateProvider } from "../contexts/TemplateContext"; -import { ElementsCreation } from "../pages/ElementsCreation"; -import { ProtocolCreation } from "../pages/ProtocolCreation"; -import { TemplateSetup } from "../pages/TemplateSetup"; -import { Layout } from "./Layout"; +import { FC } from 'react' +import { Navigate, Route, Routes } from 'react-router-dom' +import { TemplateProvider } from '../contexts/TemplateContext' +import { ElementsCreation } from '../pages/ElementsCreation' +import { ProtocolCreation } from '../pages/ProtocolCreation' +import { TemplateSetup } from '../pages/TemplateSetup' +import { Layout } from './Layout' const App: FC = () => { return ( @@ -12,13 +12,23 @@ const App: FC = () => { }> } /> - } /> - } /> - } /> + } /> + } + /> + } + /> + } + /> - ); -}; + ) +} -export default App; +export default App diff --git a/src/app/Layout/Layout.tsx b/src/app/Layout/Layout.tsx index 9774247..6328804 100644 --- a/src/app/Layout/Layout.tsx +++ b/src/app/Layout/Layout.tsx @@ -1,65 +1,15 @@ -import { FileText, Settings, Wrench } from "lucide-react"; -import { FC } from "react"; -import { Link, Outlet, useLocation } from "react-router-dom"; +import { FC } from 'react' +import { Outlet } from 'react-router-dom' const Layout: FC = () => { - const location = useLocation(); - return ( -
- {/* Верхняя навигация */} - - +
{/* Основной контент */}
- ); -}; + ) +} -export default Layout; +export default Layout diff --git a/src/components/BasicElements/CheckboxElement.tsx b/src/components/BasicElements/CheckboxElement.tsx index aaab3c1..430732e 100644 --- a/src/components/BasicElements/CheckboxElement.tsx +++ b/src/components/BasicElements/CheckboxElement.tsx @@ -16,7 +16,7 @@ export const checkboxDefinition: ElementDefinition = { required: false, targetCells: [], }, - Editor: ({ config, onChange }) => ( + Editor: () => (
Дополнительные настройки отсутствуют
diff --git a/src/components/BasicElements/NumberElement.tsx b/src/components/BasicElements/NumberElement.tsx index ebdaf0d..ead0b39 100644 --- a/src/components/BasicElements/NumberElement.tsx +++ b/src/components/BasicElements/NumberElement.tsx @@ -17,7 +17,7 @@ export const numberDefinition: ElementDefinition = { required: false, targetCells: [], }, - Editor: ({ config, onChange }) => ( + Editor: () => (
Дополнительные настройки отсутствуют
diff --git a/src/components/BasicElements/TextElement.tsx b/src/components/BasicElements/TextElement.tsx index 96e898f..6733ad5 100644 --- a/src/components/BasicElements/TextElement.tsx +++ b/src/components/BasicElements/TextElement.tsx @@ -17,7 +17,7 @@ export const textDefinition: ElementDefinition = { required: false, targetCells: [], }, - Editor: ({ config, onChange }) => ( + Editor: () => (
Дополнительные настройки отсутствуют
diff --git a/src/components/BasicElements/TextareaElement.tsx b/src/components/BasicElements/TextareaElement.tsx index b23f3a6..2506f68 100644 --- a/src/components/BasicElements/TextareaElement.tsx +++ b/src/components/BasicElements/TextareaElement.tsx @@ -17,7 +17,7 @@ export const textareaDefinition: ElementDefinition = { required: false, targetCells: [], }, - Editor: ({ config, onChange }) => ( + Editor: () => (
Дополнительные настройки отсутствуют
diff --git a/src/components/DualSpreadsheet/DualSpreadsheet.tsx b/src/components/DualSpreadsheet/DualSpreadsheet.tsx index cf53781..02f3471 100644 --- a/src/components/DualSpreadsheet/DualSpreadsheet.tsx +++ b/src/components/DualSpreadsheet/DualSpreadsheet.tsx @@ -10,6 +10,7 @@ import { useState, } from 'react' import { VariableSizeGrid } from 'react-window' +import { useFormulaAutoSave } from '../../hooks/useFormulaAutoSave' import { useMultiSheetEngine } from '../../hooks/useMultiSheetEngine' import { MergedCell } from '../../types/template' @@ -80,6 +81,7 @@ const findMergedCellInfo = ( interface DualSpreadsheetProps { templateData?: Record> mergedCells?: MergedCell[] // Добавляем поддержку объединенных ячеек + templateId?: string // Добавляем ID шаблона для сохранения } // Кэшированные данные ячеек для оптимизации @@ -147,9 +149,19 @@ const Cell = memo( } const className = `border border-border ${isSelected ? 'bg-accent' : ''} ${ - isModified ? 'bg-yellow-50' : '' + isModified ? 'bg-yellow-400 !important' : '' }` + // Отладка стилей для измененных ячеек + if (isModified) { + console.log(`🎨 Стили для измененной ячейки ${rowIndex},${colIndex}:`, { + className, + isModified, + isSelected, + displayValue, + }) + } + // Для объединенных ячеек нужно рассчитать правильные размеры let cellStyle: CSSProperties = { ...style, @@ -185,7 +197,7 @@ const Cell = memo( backgroundColor: isSelected ? 'hsl(var(--accent))' : isModified - ? '#fefce8' + ? '#fef3c7' : 'white', } } @@ -276,6 +288,12 @@ interface FormulaBarProps { onBlur: (e: React.FocusEvent) => void onKeyDown: (e: React.KeyboardEvent) => void formulaBarRef: (el: HTMLInputElement | null) => void + // Обновляем props для сохранения + isSaving?: boolean + hasUnsavedChanges?: boolean + lastSaveError?: string | null + onManualSave?: () => void + onClearError?: () => void } const FormulaBar = memo( @@ -289,9 +307,12 @@ const FormulaBar = memo( onBlur, onKeyDown, formulaBarRef, + isSaving = false, + hasUnsavedChanges = false, + lastSaveError = null, + onManualSave, + onClearError, }: Omit) => { - // Теперь FormulaBar всегда отображается, так как он один - return (
@@ -341,7 +362,7 @@ const FormulaBar = memo( }} onKeyDown={onKeyDown} /> - {isCalculating && ( + {(isCalculating || isSaving) && (
+ + {isSaving ? 'Сохранение...' : 'Расчет...'} +
)} + {/* Ошибка сохранения */} + {lastSaveError && ( +
+ + + + + Ошибка сохранения + + {onClearError && ( + + )} +
+ )} + {/* Кнопка ручного сохранения */} + {onManualSave && ( + + )}
@@ -414,6 +522,19 @@ const CellRenderer = ({ ? '' // Не показывать вычисленное значение во время редактирования : cellData?.displayValue || '' + // Отладка для измененных ячеек + if (cellData?.isModified) { + console.log( + `🔍 CellRenderer: Измененная ячейка ${rowIndex},${columnIndex}:`, + { + cellKey, + isModified: cellData.isModified, + displayValue, + rawValue, + }, + ) + } + // Получаем информацию об объединенных ячейках только для листа отчета const mergedCellInfo = sheetType === 'report' && mergedCells @@ -565,29 +686,28 @@ const Spreadsheet = ({ >
(containerRefs.current[sheetType] = el)} - className="flex h-full flex-col overflow-hidden rounded-lg border bg-card shadow-sm" + className="flex h-full flex-col overflow-hidden border bg-card shadow-sm" onKeyDown={handleKeyDown} tabIndex={0} > - {/* Header */} -
-
-
-

- {sheetType === 'report' ? 'Отчет' : 'Расчеты'} -

-
-
- {/* Spreadsheet Body */}
{/* Row Headers */}
-
+
+ {/* Компактное обозначение листа */} +
+ + {SHEET_NAMES[sheetType]} + +
+
(rowHeaderRefs.current[sheetType] = el)} className="overflow-y-hidden" @@ -675,6 +795,7 @@ const Spreadsheet = ({ const DualSpreadsheet: FC = ({ templateData = {}, mergedCells = [], + templateId, }) => { const { revision, updateRevision, ...multiSheetEngine } = useMultiSheetEngine( { @@ -693,6 +814,7 @@ const DualSpreadsheet: FC = ({ } | null>(null) const [isEditing, setIsEditing] = useState(false) const [editingValue, setEditingValue] = useState('') // Локальное значение во время редактирования + const [isInitialized, setIsInitialized] = useState(false) const [columnWidths, setColumnWidths] = useState>( () => ({ @@ -729,6 +851,11 @@ const DualSpreadsheet: FC = ({ useEffect(() => { const reportTemplate = templateData?.[SHEET_NAMES.report] + // Минимальная отладка templateData + if (!templateData || Object.keys(templateData).length === 0) { + console.log('⚠️ templateData пустой') + } + if (reportTemplate) { // Очищаем старые значения initialTemplateValues.current = {} @@ -736,6 +863,13 @@ const DualSpreadsheet: FC = ({ Object.entries(reportTemplate).forEach(([cellName, value]) => { initialTemplateValues.current[cellName] = String(value) }) + + console.log( + '✅ Загружено значений шаблона:', + Object.keys(initialTemplateValues.current).length, + ) + } else { + console.log('⚠️ DualSpreadsheet: Данные шаблона отчета не найдены') } }, [templateData]) @@ -793,6 +927,19 @@ const DualSpreadsheet: FC = ({ const cellName = getColumnLabel(col) + (row + 1) const templateValue = initialTemplateValues.current[cellName] ?? '' + // Отладка только для измененных ячеек + if ( + sheetType === 'report' && + templateValue && + rawValue !== templateValue + ) { + console.log(`🔧 Измененная ячейка ${cellName}:`, { + templateValue, + rawValue, + isModified: true, + }) + } + // Нормализуем значения для сравнения const normalizeValue = (value: any): string => { if (value === null || value === undefined) return '' @@ -810,6 +957,15 @@ const DualSpreadsheet: FC = ({ normalizedTemplate !== '' && normalizedRaw !== normalizedTemplate + // Отладка только для измененных ячеек + if (sheetType === 'report' && isModified) { + console.log(`🔍 Измененная ячейка ${cellName}:`, { + templateValue, + rawValue, + isModified, + }) + } + cachedData[cellKey] = { displayValue, rawValue, @@ -888,6 +1044,24 @@ const DualSpreadsheet: FC = ({ (save: boolean) => { if (save && selectedCell) { const sheetName = SHEET_NAMES[activeSheet] + const cellName = + getColumnLabel(selectedCell.col) + (selectedCell.row + 1) + const oldValue = multiSheetEngine.getCellRawValue( + sheetName, + selectedCell.row, + selectedCell.col, + ) + const templateValue = initialTemplateValues.current[cellName] ?? '' + + console.log('💾 Сохранение ячейки:', { + cellName, + oldValue, + newValue: editingValue, + templateValue, + isChanged: oldValue !== editingValue, + isModifiedFromTemplate: templateValue !== editingValue, + }) + multiSheetEngine.setCellValueWithoutRecalc( sheetName, selectedCell.row, @@ -1093,6 +1267,85 @@ const DualSpreadsheet: FC = ({ R: 0.5, }) + // Используем новый хук автосохранения + const { + isSaving, + hasUnsavedChanges, + lastSaveError, + manualSave, + clearError, + loadSavedFormulas, + } = useFormulaAutoSave( + multiSheetEngine.getAllModifiedCells, + multiSheetEngine.isCalculating, + revision, + { + templateId, + autoSaveDelay: 2000, + enableAutoSave: true, + }, + ) + + // Загружаем сохраненные формулы при инициализации + useEffect(() => { + if (!templateId || isInitialized) return + + // Проверяем, есть ли начальные данные + const hasInitialData = + Object.keys(templateData).length > 0 && + Object.values(templateData).some((sheet) => Object.keys(sheet).length > 0) + + // Всегда загружаем сохраненные данные, если они есть + const savedFormulas = loadSavedFormulas() + + if (savedFormulas && Object.keys(savedFormulas).length > 0) { + console.log('🔄 Загружаем сохраненные формулы в движок:', savedFormulas) + + if (hasInitialData) { + // Если есть начальные данные, СЛИВАЕМ их с сохраненными изменениями + console.log('📋 Сливаем данные шаблона с сохраненными изменениями') + + // Создаем объединенные данные: шаблон + сохраненные изменения + const mergedData: Record> = {} + + // Копируем данные шаблона + Object.entries(templateData).forEach(([sheetName, sheetData]) => { + mergedData[sheetName] = { ...sheetData } + }) + + // Применяем сохраненные изменения поверх шаблона + Object.entries(savedFormulas).forEach(([sheetName, sheetData]) => { + if (!mergedData[sheetName]) { + mergedData[sheetName] = {} + } + Object.entries(sheetData).forEach(([cellAddress, value]) => { + mergedData[sheetName][cellAddress] = value + }) + }) + + console.log('📊 Загружаем объединенные данные (шаблон + изменения)') + multiSheetEngine.loadData(mergedData) + } else { + // Если нет начальных данных, используем только сохраненные + multiSheetEngine.loadData(savedFormulas) + } + + console.log('✅ Сохраненные формулы загружены в движок') + } else if (hasInitialData) { + console.log('📋 Используем только начальные данные шаблона') + } else { + console.log('⚠️ Нет данных для загрузки') + } + + setIsInitialized(true) + }, [ + templateId, + templateData, + loadSavedFormulas, + multiSheetEngine, + isInitialized, + ]) + // --- Spreadsheet Component --- useEffect(() => { @@ -1150,8 +1403,13 @@ const DualSpreadsheet: FC = ({ formulaBarRef={(el) => { formulaBarRef.current = el }} + isSaving={isSaving} + hasUnsavedChanges={hasUnsavedChanges} + lastSaveError={lastSaveError} + onManualSave={manualSave} + onClearError={clearError} /> -
+
void - onCancel: () => void onBack: () => void } export const TemplateEditor: React.FC = ({ template, - onSave, - onCancel, onBack, }) => { const [editedTemplate, setEditedTemplate] = useState