API работает мать его

This commit is contained in:
2025-07-20 18:36:02 +03:00
parent 71b5ea4e7b
commit f162bcf1de
5 changed files with 120 additions and 26 deletions

View File

@@ -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(
'📋 Используем только базовые данные (нет пользовательских изменений)'

View File

@@ -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',
}
}

View File

@@ -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}

View File

@@ -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,
}
)
}

View File

@@ -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(),
},
}))