API работает мать его
This commit is contained in:
@@ -194,9 +194,7 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
||||
const normalizedRaw = normalizeValue(rawValue)
|
||||
|
||||
const isModified =
|
||||
sheetType === 'report' &&
|
||||
normalizedBaseFile !== '' &&
|
||||
normalizedRaw !== normalizedBaseFile
|
||||
sheetType === 'report' && normalizedRaw !== normalizedBaseFile
|
||||
|
||||
// Отладка только для измененных ячеек
|
||||
if (sheetType === 'report' && isModified) {
|
||||
@@ -582,6 +580,8 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
||||
if (Object.keys(fileData.data).length > 0) {
|
||||
finalBaseData = { [SHEET_NAMES.report]: fileData.data }
|
||||
setBaseFileData(finalBaseData)
|
||||
// Сохраняем baseline, чтобы в L попадали только пользовательские изменения
|
||||
setTemplateData(finalBaseData)
|
||||
console.log('📄 Загружены базовые данные файла:', finalBaseData)
|
||||
} else {
|
||||
console.log(
|
||||
@@ -619,8 +619,22 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
||||
// Преобразуем листы API в формат данных
|
||||
const userChanges: Record<string, Record<string, any>> = {}
|
||||
Object.entries(apiSheets).forEach(([sheetName, sheet]) => {
|
||||
if (sheet.cells && Object.keys(sheet.cells).length > 0) {
|
||||
userChanges[sheetName] = sheet.cells
|
||||
// Нормализуем имя листа: "Report" -> "L"
|
||||
const normalizedName =
|
||||
sheetName === 'Report'
|
||||
? SHEET_NAMES.report
|
||||
: sheetName === 'Calculations'
|
||||
? SHEET_NAMES.calculations
|
||||
: sheetName
|
||||
|
||||
// Интересуют только листы L и R
|
||||
if (
|
||||
(normalizedName === SHEET_NAMES.report ||
|
||||
normalizedName === SHEET_NAMES.calculations) &&
|
||||
sheet.cells &&
|
||||
Object.keys(sheet.cells).length > 0
|
||||
) {
|
||||
userChanges[normalizedName] = sheet.cells
|
||||
}
|
||||
})
|
||||
|
||||
@@ -657,6 +671,10 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
||||
'📊 Загружаем объединенные данные (базовые + пользовательские)'
|
||||
)
|
||||
multiSheetEngine.loadData(mergedData)
|
||||
|
||||
// Обновляем baseline, включая уже сохранённые изменения пользователя,
|
||||
// чтобы их не сохранять повторно
|
||||
setTemplateData(mergedData)
|
||||
} else if (Object.keys(finalBaseData).length > 0) {
|
||||
console.log(
|
||||
'📋 Используем только базовые данные (нет пользовательских изменений)'
|
||||
|
||||
@@ -29,7 +29,7 @@ export const Cell = memo(
|
||||
}
|
||||
|
||||
const className = `border border-border ${isSelected ? 'bg-accent' : ''} ${
|
||||
isModified ? 'bg-yellow-400 !important' : ''
|
||||
isModified && !mergedCellInfo?.isMerged ? 'bg-yellow-200' : ''
|
||||
}`
|
||||
|
||||
// Отладка стилей для измененных ячеек
|
||||
@@ -77,7 +77,7 @@ export const Cell = memo(
|
||||
backgroundColor: isSelected
|
||||
? 'hsl(var(--accent))'
|
||||
: isModified
|
||||
? '#fef3c7'
|
||||
? '#fef08a' /* тот же жёлтый оттенок, что и bg-yellow-200 */
|
||||
: 'white',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { parseExcelFile } from '@/services/fileApiSevice'
|
||||
import {
|
||||
getFileData,
|
||||
getLatestFileForTemplate,
|
||||
parseExcelFile,
|
||||
} from '@/services/fileApiSevice'
|
||||
import { Template } from '@/types/template'
|
||||
import React, { useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { ExcelUploadPanel } from './ExcelUploadPanel'
|
||||
import { FormattingToolbar } from './FormattingToolbar'
|
||||
import { HeaderBar } from './HeaderBar'
|
||||
@@ -21,6 +25,50 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
|
||||
)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [dataVersion, setDataVersion] = useState(0)
|
||||
const [serverFileName, setServerFileName] = useState<string | undefined>()
|
||||
|
||||
// При монтировании пытаемся загрузить последний файл для шаблона
|
||||
useEffect(() => {
|
||||
let isMounted = true
|
||||
|
||||
const loadLatestFile = async () => {
|
||||
try {
|
||||
const latestFile = await getLatestFileForTemplate(template.id)
|
||||
if (!latestFile) return
|
||||
|
||||
// Сохраняем имя файла для отображения
|
||||
if (isMounted) {
|
||||
setServerFileName(latestFile.name)
|
||||
}
|
||||
|
||||
// Если у нас еще нет данных Excel – загружаем их
|
||||
if (
|
||||
Object.keys(excelData).length === 0 &&
|
||||
latestFile &&
|
||||
latestFile.id
|
||||
) {
|
||||
const fileData = await getFileData(latestFile.id)
|
||||
|
||||
if (isMounted && Object.keys(fileData.data).length > 0) {
|
||||
setExcelData(fileData.data)
|
||||
setEditedTemplate(prev => ({
|
||||
...prev,
|
||||
mergedCells: fileData.mergedCells,
|
||||
}))
|
||||
setDataVersion(v => v + 1)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка при загрузке последнего файла шаблона:', error)
|
||||
}
|
||||
}
|
||||
|
||||
loadLatestFile()
|
||||
|
||||
return () => {
|
||||
isMounted = false
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0]
|
||||
@@ -60,7 +108,7 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
|
||||
<div className="flex items-center justify-between">
|
||||
<FormattingToolbar />
|
||||
<ExcelUploadPanel
|
||||
fileName={editedTemplate.excelFile?.name}
|
||||
fileName={serverFileName || editedTemplate.excelFile?.name}
|
||||
isLoading={isLoading}
|
||||
onUploadClick={() => {}}
|
||||
onFileChange={handleFileChange}
|
||||
|
||||
@@ -233,35 +233,46 @@ export function useMultiSheetEngine({
|
||||
const allData = workbookRef.current.toJSON()
|
||||
const modifiedData: Record<string, Record<string, CellValue>> = {}
|
||||
|
||||
// Сравниваем с изначальными данными шаблона
|
||||
Object.entries(allData).forEach(([sheetName, sheetData]) => {
|
||||
// Список всех листов (из движка или baseline)
|
||||
const sheetNamesSet = new Set([
|
||||
...Object.keys(allData),
|
||||
...Object.keys(templateDataRef.current),
|
||||
])
|
||||
|
||||
sheetNamesSet.forEach(sheetName => {
|
||||
const sheetData = allData[sheetName] || {}
|
||||
const templateSheet = templateDataRef.current[sheetName] || {}
|
||||
|
||||
const cellAddresses = new Set([
|
||||
...Object.keys(sheetData),
|
||||
...Object.keys(templateSheet),
|
||||
])
|
||||
|
||||
const modifiedSheet: Record<string, CellValue> = {}
|
||||
|
||||
Object.entries(sheetData).forEach(([cellAddress, value]) => {
|
||||
const templateValue = templateSheet[cellAddress]
|
||||
cellAddresses.forEach(cellAddress => {
|
||||
const currentVal = sheetData[cellAddress] ?? ''
|
||||
const templateVal = templateSheet[cellAddress] ?? ''
|
||||
|
||||
// Нормализуем значения для сравнения
|
||||
const normalizeValue = (val: any): string => {
|
||||
if (val === null || val === undefined) return ''
|
||||
const str = String(val).trim()
|
||||
// Считаем "0" и пустую строку эквивалентными
|
||||
if (str === '0' || str === '') return ''
|
||||
return str
|
||||
}
|
||||
|
||||
const normalizedCurrent = normalizeValue(value)
|
||||
const normalizedTemplate = normalizeValue(templateValue)
|
||||
const normalizedCurrent = normalizeValue(currentVal)
|
||||
const normalizedTemplate = normalizeValue(templateVal)
|
||||
|
||||
// Если значение отличается от шаблона, считаем его модификацией
|
||||
if (normalizedCurrent !== normalizedTemplate) {
|
||||
modifiedSheet[cellAddress] = value
|
||||
// Если текущего значения нет (ячейка очищена), отправляем ''
|
||||
modifiedSheet[cellAddress] = currentVal
|
||||
console.log(
|
||||
`🔧 Пользовательское изменение ${sheetName}:${cellAddress}:`,
|
||||
{
|
||||
template: normalizedTemplate,
|
||||
current: normalizedCurrent,
|
||||
value,
|
||||
value: currentVal,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -81,7 +81,8 @@ export const useSheetAutoSave = (
|
||||
const response = await getSheetsByTemplate(cleanTemplateId)
|
||||
const sheetsMap = response.sheets.reduce(
|
||||
(acc, sheet) => {
|
||||
acc[sheet.name] = sheet
|
||||
const normalizedName = sheet.name === 'Report' ? 'L' : sheet.name
|
||||
acc[normalizedName] = { ...sheet, name: normalizedName }
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, ApiSheet>
|
||||
@@ -109,6 +110,7 @@ export const useSheetAutoSave = (
|
||||
// Основная функция сохранения через API
|
||||
const performSave = useCallback(
|
||||
async (data: Record<string, Record<string, any>>) => {
|
||||
// Сохраняем все листы, но пропускаем пустые
|
||||
const sheetNames = Object.keys(data)
|
||||
if (sheetNames.length === 0) {
|
||||
console.log('📭 Нет данных для сохранения')
|
||||
@@ -154,10 +156,25 @@ export const useSheetAutoSave = (
|
||||
}
|
||||
|
||||
if (existingSheet) {
|
||||
// Обновляем существующий лист
|
||||
console.log(`🔄 Обновляем лист "${sheetName}"`)
|
||||
// Объединяем старые и новые данные, чтобы не потерять предыдущие изменения
|
||||
const mergedCells = {
|
||||
...existingSheet.cells,
|
||||
...sheetCells,
|
||||
}
|
||||
|
||||
// Проверяем, изменилось ли что-нибудь
|
||||
const isChanged = Object.keys(sheetCells).some(
|
||||
key => existingSheet.cells[key] !== sheetCells[key]
|
||||
)
|
||||
|
||||
if (!isChanged) {
|
||||
console.log(`⏭️ Лист "${sheetName}" без новых изменений`)
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`🔄 Обновляем лист "${sheetName}" (patch)`)
|
||||
await patchSheet(existingSheet.id, {
|
||||
cells: sheetCells,
|
||||
cells: mergedCells,
|
||||
})
|
||||
|
||||
// Обновляем локальное состояние
|
||||
@@ -165,7 +182,7 @@ export const useSheetAutoSave = (
|
||||
...prev,
|
||||
[sheetName]: {
|
||||
...existingSheet,
|
||||
cells: sheetCells,
|
||||
cells: mergedCells,
|
||||
updated_at: new Date().toISOString(),
|
||||
},
|
||||
}))
|
||||
|
||||
Reference in New Issue
Block a user