diff --git a/src/app/App.tsx b/src/app/App.tsx index 8a38b9a..61bc06f 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -2,6 +2,7 @@ import { FC, useEffect } from 'react' import { Navigate, Route, Routes } from 'react-router-dom' +import Layout from '@/app/Layout' import { CategoryProvider } from '@/entity/template/model/CategoryContext' import { TemplateProvider } from '@/entity/template/model/TemplateContext' import { @@ -17,7 +18,6 @@ import { TemplatesOverviewPage, } from '@/page' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { Layout } from './Layout' const queryClient = new QueryClient() diff --git a/src/app/Layout/Layout.tsx b/src/app/Layout.tsx similarity index 86% rename from src/app/Layout/Layout.tsx rename to src/app/Layout.tsx index 6328804..ddbb80b 100644 --- a/src/app/Layout/Layout.tsx +++ b/src/app/Layout.tsx @@ -4,7 +4,6 @@ import { Outlet } from 'react-router-dom' const Layout: FC = () => { return (
- {/* Основной контент */}
diff --git a/src/app/Layout/index.ts b/src/app/Layout/index.ts deleted file mode 100644 index 4ea2400..0000000 --- a/src/app/Layout/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import Layout from "./Layout"; - -export { Layout }; diff --git a/src/app/hooks.ts b/src/app/hooks.ts deleted file mode 100644 index e1fda31..0000000 --- a/src/app/hooks.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { useDispatch, useSelector } from "react-redux"; -import type { TypedUseSelectorHook } from "react-redux"; -import type { RootState, AppDispatch } from "./store"; - -export const useAppDispatch: () => AppDispatch = useDispatch; -export const useAppSelector: TypedUseSelectorHook = useSelector; diff --git a/src/app/index.tsx b/src/app/index.tsx index 921c41f..497afe7 100644 --- a/src/app/index.tsx +++ b/src/app/index.tsx @@ -9,7 +9,6 @@ import { initializeElementRegistry } from '@/entity/element/model/interface' import App from './App' import './index.css' -// Инициализируем реестр элементов initializeElementRegistry() createRoot(document.getElementById('root') as HTMLElement).render( diff --git a/src/component/DualSpreadsheet/types.ts b/src/component/DualSpreadsheet/types.ts index 15294d7..5896e03 100644 --- a/src/component/DualSpreadsheet/types.ts +++ b/src/component/DualSpreadsheet/types.ts @@ -32,12 +32,6 @@ export interface CellSelection { endCol: number } -// Интерфейс для выделения множества ячеек -export interface MultiSelection { - sheet: SheetType - selections: CellSelection[] -} - // Кэшированные данные ячеек для оптимизации export interface CachedCellData { displayValue: string diff --git a/src/entity/element/model/implementations/VerificationConditionsElement/api.ts b/src/entity/element/model/implementations/VerificationConditionsElement/api.ts index 04cc328..f199e23 100644 --- a/src/entity/element/model/implementations/VerificationConditionsElement/api.ts +++ b/src/entity/element/model/implementations/VerificationConditionsElement/api.ts @@ -13,14 +13,11 @@ import { UpdateDailyConditionOutput, } from './types' -// Базовый URL для API (может быть настроен через переменные окружения) -const BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api' - // API для лабораторий export const laboratoriesApi = { // Получить все лаборатории getAll: async (): Promise => { - const response = await fetch(`${BASE_URL}/laboratories/`) + const response = await fetch(`api/laboratories/`) if (!response.ok) { throw new Error('Ошибка загрузки лабораторий') } @@ -30,7 +27,7 @@ export const laboratoriesApi = { // Получить лабораторию по ID getById: async (id: string): Promise => { - const response = await fetch(`${BASE_URL}/laboratories/${id}`) + const response = await fetch(`api/laboratories/${id}`) if (!response.ok) { if (response.status === 404) { return null @@ -60,8 +57,8 @@ export const dailyConditionsApi = { } const url = searchParams.toString() - ? `${BASE_URL}/daily-conditions/?${searchParams}` - : `${BASE_URL}/daily-conditions/` + ? `api/daily-conditions/?${searchParams}` + : `api/daily-conditions/` const response = await fetch(url) if (!response.ok) { @@ -73,7 +70,7 @@ export const dailyConditionsApi = { // Получить конкретное условие по ID getById: async (id: string): Promise => { - const response = await fetch(`${BASE_URL}/daily-conditions/${id}`) + const response = await fetch(`api/daily-conditions/${id}`) if (!response.ok) { if (response.status === 404) { return null @@ -86,7 +83,7 @@ export const dailyConditionsApi = { // Создать новые условия create: async (input: CreateDailyConditionInput): Promise => { - const response = await fetch(`${BASE_URL}/daily-conditions/`, { + const response = await fetch(`api/daily-conditions/`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -108,7 +105,7 @@ export const dailyConditionsApi = { id: string, input: UpdateDailyConditionInput ): Promise => { - const response = await fetch(`${BASE_URL}/daily-conditions/${id}`, { + const response = await fetch(`api/daily-conditions/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', @@ -124,7 +121,7 @@ export const dailyConditionsApi = { // Удалить условия delete: async (id: string): Promise => { - const response = await fetch(`${BASE_URL}/daily-conditions/${id}`, { + const response = await fetch(`api/daily-conditions/${id}`, { method: 'DELETE', }) if (!response.ok) { @@ -203,56 +200,3 @@ export const elementApi = { return savedCondition }, } - -// Вспомогательные функции -export const verificationConditionsUtils = { - // Валидация значения условия - validateConditionValue: ( - value: any, - dataType: string = 'string', - minValue?: number, - maxValue?: number - ): { isValid: boolean; error?: string } => { - if (dataType === 'number') { - const numValue = parseFloat(value) - if (isNaN(numValue)) { - return { isValid: false, error: 'Должно быть числом' } - } - if (minValue !== undefined && numValue < minValue) { - return { isValid: false, error: `Минимальное значение: ${minValue}` } - } - if (maxValue !== undefined && numValue > maxValue) { - return { isValid: false, error: `Максимальное значение: ${maxValue}` } - } - } - return { isValid: true } - }, - - // Форматирование значения с единицей измерения - formatValueWithUnit: (value: any, unit?: string): string => { - if (value === undefined || value === null || value === '') { - return '-' - } - return unit ? `${value} ${unit}` : value.toString() - }, - - // Получение ключа условия для хранения в conditions JSON (обычно используется ID типа условия) - getConditionKey: (conditionTypeId: string): string => { - return conditionTypeId - }, - - // Генерация ключа на основе названия условия (для обратной совместимости) - generateConditionKey: (conditionTypeName: string): string => { - return conditionTypeName - .toLowerCase() - .replace(/\s+/g, '_') - .replace(/[^\w]/g, '') - }, - - // Проверка, является ли строка валидным UUID - isValidUUID: (str: string): boolean => { - const uuidRegex = - /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i - return uuidRegex.test(str) - }, -} diff --git a/src/entity/element/model/implementations/VerificationConditionsElement/definition.tsx b/src/entity/element/model/implementations/VerificationConditionsElement/definition.tsx index b3c8781..fcdcddc 100644 --- a/src/entity/element/model/implementations/VerificationConditionsElement/definition.tsx +++ b/src/entity/element/model/implementations/VerificationConditionsElement/definition.tsx @@ -9,7 +9,6 @@ import { VerificationConditionsEditor } from './VerificationConditionsEditor' import { VerificationConditionsPreview } from './VerificationConditionsPreview' import { VerificationConditionsRender } from './VerificationConditionsRender' -// Форматирование даты как в функции ДАТАПРОТОКОЛА const formatVerificationDate = (dateStr: string): string => { try { const months = [ diff --git a/src/feature/excelEngine/excelEngine.ts b/src/feature/excelEngine/excelEngine.ts index 3185ee2..3ed9e7f 100644 --- a/src/feature/excelEngine/excelEngine.ts +++ b/src/feature/excelEngine/excelEngine.ts @@ -4,13 +4,12 @@ import { VOLATILE_FUNCTIONS, } from './functions' -// Регулярные выражения для ссылок // Ограничиваем поиск ячеек диапазоном A1-Z90 -const QUALIFIED_REF_RE = /\b[A-Za-z0-9_]+![A-Z]([1-9]|[1-8][0-9]|90)\b/g // SheetName!A1 до SheetName!Z90 -const LOCAL_REF_RE = /\b[A-Z]([1-9]|[1-8][0-9]|90)\b/g // A1 до Z90 -const RANGE_RE = /\b[A-Z]([1-9]|[1-8][0-9]|90):[A-Z]([1-9]|[1-8][0-9]|90)\b/g // A1:B2, D5:D7, etc. в диапазоне A1-Z90 +const QUALIFIED_REF_RE = /\b[A-Za-z0-9_]+![A-Z]([1-9]|[1-8][0-9]|90)\b/g +const LOCAL_REF_RE = /\b[A-Z]([1-9]|[1-8][0-9]|90)\b/g +const RANGE_RE = /\b[A-Z]([1-9]|[1-8][0-9]|90):[A-Z]([1-9]|[1-8][0-9]|90)\b/g const QUALIFIED_RANGE_RE = - /\b[A-Za-z0-9_]+![A-Z]([1-9]|[1-8][0-9]|90):[A-Z]([1-9]|[1-8][0-9]|90)\b/g // SheetName!A1:B2 в диапазоне A1-Z90 + /\b[A-Za-z0-9_]+![A-Z]([1-9]|[1-8][0-9]|90):[A-Z]([1-9]|[1-8][0-9]|90)\b/g // Типы для значений ячеек export type CellValue = string | number | boolean | null | undefined @@ -598,10 +597,6 @@ export class Workbook { } } - removeFunction(name: string): void { - delete this._functions[name.toUpperCase()] - } - getFunctions(): Record { return this._functions } @@ -641,14 +636,6 @@ export class Workbook { // Утилиты для работы с координатами ячеек export class CellUtils { - static columnToIndex(column: string): number { - let result = 0 - for (let i = 0; i < column.length; i++) { - result = result * 26 + (column.charCodeAt(i) - 65 + 1) - } - return result - 1 - } - static indexToColumn(index: number): string { let result = '' while (index >= 0) { @@ -658,17 +645,6 @@ export class CellUtils { return result } - static parseCellName(cellName: string): { column: string; row: number } { - const match = cellName.match(/^([A-Z]+)(\d+)$/) - if (!match) { - throw new Error(`Invalid cell name: ${cellName}`) - } - return { - column: match[1], - row: parseInt(match[2], 10), - } - } - static createCellName(column: string | number, row: number): string { const col = typeof column === 'number' ? CellUtils.indexToColumn(column) : column diff --git a/src/hook/useFileQueries.ts b/src/hook/useFileQueries.ts index e53c389..45a1020 100644 --- a/src/hook/useFileQueries.ts +++ b/src/hook/useFileQueries.ts @@ -1,10 +1,5 @@ -import { - getFileData, - getFilesByTemplateId, - getLatestFileForTemplate, - parseExcelFile, -} from '@/service/fileApiService' -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { getFileData, getLatestFileForTemplate } from '@/service/fileApiService' +import { useQuery } from '@tanstack/react-query' // Ключи для React Query export const fileKeys = { @@ -16,17 +11,6 @@ export const fileKeys = { data: (fileId: string) => [...fileKeys.all, 'data', fileId] as const, } -// Хук для получения файлов по template_id с кешированием -export function useFilesByTemplate(templateId: string | undefined) { - return useQuery({ - queryKey: fileKeys.byTemplate(templateId || ''), - queryFn: () => getFilesByTemplateId(templateId!), - enabled: !!templateId, - staleTime: 5 * 60 * 1000, // 5 минут - gcTime: 10 * 60 * 1000, // 10 минут кеш - }) -} - // Хук для получения последнего файла для шаблона export function useLatestFileForTemplate(templateId: string | undefined) { return useQuery({ @@ -48,38 +32,3 @@ export function useFileData(fileId: string | undefined) { gcTime: 30 * 60 * 1000, // 30 минут кеш }) } - -// Хук для загрузки Excel файла -export function useParseExcelFile() { - const queryClient = useQueryClient() - - return useMutation({ - mutationFn: ({ file, templateId }: { file: File; templateId: string }) => - parseExcelFile(file, templateId), - onSuccess: (data, variables) => { - // Инвалидируем связанные кеши после успешной загрузки - queryClient.invalidateQueries({ - queryKey: fileKeys.byTemplate(variables.templateId), - }) - queryClient.invalidateQueries({ - queryKey: fileKeys.latest(variables.templateId), - }) - - // Сохраняем данные файла в кеш - queryClient.setQueryData(fileKeys.data(data.fileId), data) - }, - }) -} - -// Хук для предзагрузки данных файла -export function usePrefetchFileData() { - const queryClient = useQueryClient() - - return (fileId: string) => { - queryClient.prefetchQuery({ - queryKey: fileKeys.data(fileId), - queryFn: () => getFileData(fileId), - staleTime: 10 * 60 * 1000, - }) - } -} diff --git a/src/hook/useMultiSheetEngine.ts b/src/hook/useMultiSheetEngine.ts index 88bf628..699ecba 100644 --- a/src/hook/useMultiSheetEngine.ts +++ b/src/hook/useMultiSheetEngine.ts @@ -69,14 +69,7 @@ export function useMultiSheetEngine({ if (!workbookRef.current) return // Предотвращаем случайную очистку данных при смене вкладки - if (document.hidden) { - if (process.env.NODE_ENV !== 'production') { - console.log( - '⚠️ useMultiSheetEngine: предотвращаем загрузку данных когда страница скрыта' - ) - } - return - } + if (document.hidden) return // Создаем ключ для проверки изменений данных const dataKey = JSON.stringify(initialData) @@ -85,21 +78,10 @@ export function useMultiSheetEngine({ if ( loadedDataRef.current === dataKey && Object.keys(initialData).length > 0 - ) { - if (process.env.NODE_ENV !== 'production') { - console.log('⏭️ useMultiSheetEngine: данные уже загружены, пропускаем') - } + ) return - } if (Object.keys(initialData).length > 0) { - if (process.env.NODE_ENV !== 'production') { - console.log( - '🔄 useMultiSheetEngine: загрузка initialData:', - Object.keys(initialData) - ) - } - // Очищаем только те листы, для которых есть новые данные for (const sheetName of Object.keys(initialData)) { const sheet = workbookRef.current.getSheet(sheetName) @@ -117,42 +99,6 @@ export function useMultiSheetEngine({ } }, [initialData, safeRecalc]) - // Синхронизация всех листов из движка - // const syncAllSheetsFromEngine = useCallback(() => { - // if (!workbookRef.current || isRecalculatingRef.current) return - - // const newSheetCells: Record = {} - - // sheets.forEach((sheetConfig) => { - // const sheet = workbookRef.current!.getSheet(sheetConfig.name) - // if (!sheet) return - - // const cells = Array(sheetConfig.rows) - // .fill(null) - // .map(() => - // Array(sheetConfig.cols) - // .fill(null) - // .map(() => ({ value: '', isSelected: false })), - // ) - - // // Получаем все ячейки из движка - // for (let row = 0; row < sheetConfig.rows; row++) { - // for (let col = 0; col < sheetConfig.cols; col++) { - // const cellName = columnToLabel(col) + (row + 1) - // const cell = sheet.getCell(cellName) - - // if (cell.raw !== null && cell.raw !== undefined) { - // cells[row][col] = String(cell.raw) - // } - // } - // } - - // newSheetCells[sheetConfig.name] = cells - // }) - - // // setSheetCells(newSheetCells) // Удалено, так как sheetCells убрано - // }, [sheets]) - // Отложенный пересчет с дебаунсом const debouncedRecalc = useCallback(() => { if (!workbookRef.current || isRecalculatingRef.current) return @@ -276,15 +222,6 @@ export function useMultiSheetEngine({ const setTemplateData = useCallback( (data: Record>) => { templateDataRef.current = JSON.parse(JSON.stringify(data)) // Глубокая копия - if (process.env.NODE_ENV !== 'production') { - console.log('📋 Установлены базовые данные шаблона:', { - sheets: Object.keys(templateDataRef.current), - cellCount: Object.values(templateDataRef.current).reduce( - (total, sheet) => total + Object.keys(sheet).length, - 0 - ), - }) - } }, [] ) @@ -333,16 +270,6 @@ export function useMultiSheetEngine({ if (normalizedCurrent !== normalizedTemplate) { // Если текущего значения нет (ячейка очищена), отправляем '' modifiedSheet[cellAddress] = currentVal - if (process.env.NODE_ENV !== 'production') { - console.log( - `🔧 Пользовательское изменение ${sheetName}:${cellAddress}:`, - { - template: normalizedTemplate, - current: normalizedCurrent, - value: currentVal, - } - ) - } } }) @@ -351,22 +278,6 @@ export function useMultiSheetEngine({ } }) - if (process.env.NODE_ENV !== 'production') { - const totalModified = Object.values(modifiedData).reduce( - (total, sheet) => total + Object.keys(sheet).length, - 0 - ) - console.log(`📊 Найдено ${totalModified} измененных ячеек:`, { - sheets: Object.keys(modifiedData), - details: Object.fromEntries( - Object.entries(modifiedData).map(([sheetName, cells]) => [ - sheetName, - Object.keys(cells).length, - ]) - ), - }) - } - return modifiedData }, []) @@ -405,7 +316,6 @@ export function useMultiSheetEngine({ }, []) return { - // sheetCells убран setCellValue, setCellValueWithoutRecalc, debouncedRecalc, diff --git a/src/hook/useSheetQueries.ts b/src/hook/useSheetQueries.ts index 24b44cb..599a23f 100644 --- a/src/hook/useSheetQueries.ts +++ b/src/hook/useSheetQueries.ts @@ -1,7 +1,5 @@ import { createSheet, - getSheet, - getSheets, getSheetsByTemplate, patchSheet, type ApiSheet, @@ -18,16 +16,6 @@ export const sheetKeys = { detail: (sheetId: string) => [...sheetKeys.all, 'detail', sheetId] as const, } -// Хук для получения всех листов -export function useSheets() { - return useQuery({ - queryKey: sheetKeys.all, - queryFn: getSheets, - staleTime: 2 * 60 * 1000, // 2 минуты - gcTime: 10 * 60 * 1000, // 10 минут кеш - }) -} - // Хук для получения листов по template_id с кешированием export function useSheetsByTemplate(templateId: string | undefined) { return useQuery({ @@ -40,17 +28,6 @@ export function useSheetsByTemplate(templateId: string | undefined) { }) } -// Хук для получения конкретного листа -export function useSheet(sheetId: string | undefined) { - return useQuery({ - queryKey: sheetKeys.detail(sheetId || ''), - queryFn: () => getSheet(sheetId!), - enabled: !!sheetId, - staleTime: 5 * 60 * 1000, // 5 минут - gcTime: 10 * 60 * 1000, // 10 минут кеш - }) -} - // Хук для создания листа export function useCreateSheet() { const queryClient = useQueryClient() @@ -149,48 +126,3 @@ export function usePatchSheet() { }, }) } - -// Хук для массового обновления листов (для автосохранения) -export function useBatchUpdateSheets() { - const queryClient = useQueryClient() - - return useMutation({ - mutationFn: async ( - updates: Array<{ - sheetId: string - data: PatchSheetInput - templateId: string - }> - ) => { - // Выполняем все обновления параллельно - const results = await Promise.allSettled( - updates.map(({ sheetId, data }) => patchSheet(sheetId, data)) - ) - - return results - }, - onSuccess: (results, variables) => { - // Обновляем кеши для всех затронутых шаблонов - const templateIds = new Set(variables.map(v => v.templateId)) - - templateIds.forEach(templateId => { - queryClient.invalidateQueries({ - queryKey: sheetKeys.byTemplate(templateId), - }) - }) - }, - }) -} - -// Хук для предзагрузки листов шаблона -export function usePrefetchSheetsByTemplate() { - const queryClient = useQueryClient() - - return (templateId: string) => { - queryClient.prefetchQuery({ - queryKey: sheetKeys.byTemplate(templateId), - queryFn: () => getSheetsByTemplate(templateId), - staleTime: 1 * 60 * 1000, - }) - } -} diff --git a/src/hook/useSpreadsheetEngine.ts b/src/hook/useSpreadsheetEngine.ts deleted file mode 100644 index 6e07fa7..0000000 --- a/src/hook/useSpreadsheetEngine.ts +++ /dev/null @@ -1,335 +0,0 @@ -import { useCallback, useEffect, useRef, useState } from 'react' -import { - CellUtils, - CellValue, - Workbook, -} from '../feature/excelEngine/excelEngine' - -interface CellData { - value: string - isSelected: boolean -} - -interface UseSpreadsheetEngineProps { - rows?: number - cols?: number - sheetName?: string - initialData?: string[][] -} - -export function useSpreadsheetEngine({ - rows = 50, - cols = 20, - sheetName = 'Sheet1', - initialData, -}: UseSpreadsheetEngineProps = {}) { - const workbookRef = useRef(null) - const [cells, setCells] = useState(() => { - return Array(rows) - .fill(null) - .map(() => - Array(cols) - .fill(null) - .map(() => ({ value: '', isSelected: false })) - ) - }) - const [isCalculating, setIsCalculating] = useState(false) - - // Инициализация workbook - useEffect(() => { - if (!workbookRef.current) { - workbookRef.current = new Workbook() - workbookRef.current.addSheet(sheetName) - // addCustomFunctions(workbookRef.current) - } - - // Загружаем начальные данные если они есть - if (initialData) { - loadDataFromArray(initialData) - } - }, [sheetName, initialData]) - - // Конвертация данных из движка в React состояние - const syncFromEngine = useCallback(() => { - if (!workbookRef.current) return - - const sheet = workbookRef.current.getSheet(sheetName) - if (!sheet) return - - const newCells = Array(rows) - .fill(null) - .map(() => - Array(cols) - .fill(null) - .map(() => ({ value: '', isSelected: false })) - ) - - // Получаем все ячейки из движка - for (let row = 0; row < rows; row++) { - for (let col = 0; col < cols; col++) { - const cellName = CellUtils.createCellName(col, row + 1) - const cell = sheet.getCell(cellName) - - // Показываем сырое значение (raw) в UI, а не вычисленное - if (cell.raw !== null && cell.raw !== undefined) { - newCells[row][col].value = String(cell.raw) - } - } - } - - setCells(newCells) - }, [rows, cols, sheetName]) - - // Установка значения ячейки - const setCellValue = useCallback( - (row: number, col: number, value: string) => { - if (!workbookRef.current) return - - const cellName = CellUtils.createCellName(col, row + 1) - const qualifiedName = `${sheetName}!${cellName}` - - setIsCalculating(true) - - try { - // Конвертируем пустую строку в null - const cellValue: CellValue = value === '' ? null : value - - // Если значение начинается с =, то это формула - if (typeof cellValue === 'string' && cellValue.startsWith('=')) { - workbookRef.current.setCell(qualifiedName, cellValue) - } else { - // Пытаемся конвертировать в число, если возможно - const numValue = Number(cellValue) - if (!isNaN(numValue) && cellValue !== null && cellValue !== '') { - workbookRef.current.setCell(qualifiedName, numValue) - } else { - workbookRef.current.setCell(qualifiedName, cellValue) - } - } - - // Пересчитываем все формулы - workbookRef.current.recalc() - - // Синхронизируем с React состоянием - syncFromEngine() - } catch (error) { - console.error('Error setting cell value:', error) - } finally { - setIsCalculating(false) - } - }, - [sheetName, syncFromEngine] - ) - - // Получение вычисленного значения ячейки - const getCellValue = useCallback( - (row: number, col: number): CellValue => { - if (!workbookRef.current) return null - - const cellName = CellUtils.createCellName(col, row + 1) - const qualifiedName = `${sheetName}!${cellName}` - - try { - return workbookRef.current.getValue(qualifiedName) - } catch (error) { - console.error('Error getting cell value:', error) - return '#ERROR!' - } - }, - [sheetName] - ) - - // Получение сырого значения ячейки (для отображения в строке формул) - const getCellRawValue = useCallback( - (row: number, col: number): string => { - if (!workbookRef.current) return '' - - const cellName = CellUtils.createCellName(col, row + 1) - const sheet = workbookRef.current.getSheet(sheetName) - - if (!sheet) return '' - - const cell = sheet.getCell(cellName) - return cell.raw !== null && cell.raw !== undefined ? String(cell.raw) : '' - }, - [sheetName] - ) - - // Загрузка данных из массива - const loadDataFromArray = useCallback( - (data: string[][]) => { - if (!workbookRef.current) return - - const sheet = workbookRef.current.getSheet(sheetName) - if (!sheet) return - - setIsCalculating(true) - - try { - // Очищаем лист - sheet.cells.clear() - - // Загружаем данные - for (let row = 0; row < data.length && row < rows; row++) { - for (let col = 0; col < data[row].length && col < cols; col++) { - const value = data[row][col] - if (value !== null && value !== undefined && value !== '') { - const cellName = CellUtils.createCellName(col, row + 1) - sheet.setCell(cellName, value) - } - } - } - - // Пересчитываем все формулы - workbookRef.current.recalc() - - // Синхронизируем с React состоянием - syncFromEngine() - } catch (error) { - console.error('Error loading data:', error) - } finally { - setIsCalculating(false) - } - }, - [rows, cols, sheetName, syncFromEngine] - ) - - // Экспорт данных в массив (для сохранения) - const exportToArray = useCallback((): string[][] => { - if (!workbookRef.current) return [] - - const sheet = workbookRef.current.getSheet(sheetName) - if (!sheet) return [] - - const result: string[][] = Array(rows) - .fill(null) - .map(() => Array(cols).fill('')) - - for (let row = 0; row < rows; row++) { - for (let col = 0; col < cols; col++) { - const cellName = CellUtils.createCellName(col, row + 1) - const cell = sheet.getCell(cellName) - - if (cell.raw !== null && cell.raw !== undefined) { - result[row][col] = String(cell.raw) - } - } - } - - return result - }, [rows, cols, sheetName]) - - // Экспорт в JSON для бэкенда - const exportToJSON = useCallback((): string => { - if (!workbookRef.current) return '{}' - return JSON.stringify(workbookRef.current.toJSON()) - }, []) - - // Импорт из JSON с бэкенда - const importFromJSON = useCallback( - (jsonData: string) => { - if (!workbookRef.current) return - - setIsCalculating(true) - - try { - const data = JSON.parse(jsonData) - workbookRef.current.fromJSON(data) - // addCustomFunctions(workbookRef.current) - workbookRef.current.recalc() - syncFromEngine() - } catch (error) { - console.error('Error importing from JSON:', error) - } finally { - setIsCalculating(false) - } - }, - [syncFromEngine] - ) - - // Добавление пользовательской функции - const addFunction = useCallback( - (name: string, func: (...args: any[]) => any) => { - if (!workbookRef.current) return - workbookRef.current.addFunction(name, func) - }, - [] - ) - - // Пересчёт всех формул - const recalculate = useCallback(() => { - if (!workbookRef.current) return - - setIsCalculating(true) - - try { - workbookRef.current.recalc() - syncFromEngine() - } catch (error) { - console.error('Error recalculating:', error) - } finally { - setIsCalculating(false) - } - }, [syncFromEngine]) - - // Получение информации о зависимостях ячейки - const getCellDependencies = useCallback( - (row: number, col: number) => { - if (!workbookRef.current) return { precedents: [], dependents: [] } - - const cellName = CellUtils.createCellName(col, row + 1) - const sheet = workbookRef.current.getSheet(sheetName) - - if (!sheet) return { precedents: [], dependents: [] } - - const cell = sheet.getCell(cellName) - - return { - precedents: Array.from(cell.precedents), - dependents: Array.from(cell.dependents), - } - }, - [sheetName] - ) - - // Получение статистики workbook - const getWorkbookStats = useCallback(() => { - if (!workbookRef.current) return { sheets: 0, cells: 0, formulas: 0 } - - let totalCells = 0 - let totalFormulas = 0 - - for (const sheet of workbookRef.current.sheets.values()) { - totalCells += sheet.cells.size - - for (const cell of sheet.cells.values()) { - if (typeof cell.raw === 'string' && cell.raw.startsWith('=')) { - totalFormulas++ - } - } - } - - return { - sheets: workbookRef.current.sheets.size, - cells: totalCells, - formulas: totalFormulas, - } - }, []) - - return { - cells, - setCellValue, - getCellValue, - getCellRawValue, - loadDataFromArray, - exportToArray, - exportToJSON, - importFromJSON, - addFunction, - recalculate, - getCellDependencies, - getWorkbookStats, - isCalculating, - workbook: workbookRef.current, - } -}