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