удаление неиспользуемого

This commit is contained in:
2025-07-30 18:18:19 +03:00
parent a7780279e0
commit d692689961
13 changed files with 17 additions and 659 deletions

View File

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

View File

@@ -4,7 +4,6 @@ import { Outlet } from 'react-router-dom'
const Layout: FC = () => {
return (
<div className="flex h-screen flex-col bg-gray-50">
{/* Основной контент */}
<main className="flex-1 overflow-auto">
<Outlet />
</main>

View File

@@ -1,3 +0,0 @@
import Layout from "./Layout";
export { Layout };

View File

@@ -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<RootState> = useSelector;

View File

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

View File

@@ -32,12 +32,6 @@ export interface CellSelection {
endCol: number
}
// Интерфейс для выделения множества ячеек
export interface MultiSelection {
sheet: SheetType
selections: CellSelection[]
}
// Кэшированные данные ячеек для оптимизации
export interface CachedCellData {
displayValue: string

View File

@@ -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<Laboratory[]> => {
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<Laboratory | null> => {
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<DailyCondition | null> => {
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<string> => {
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<string> => {
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<void> => {
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)
},
}

View File

@@ -9,7 +9,6 @@ import { VerificationConditionsEditor } from './VerificationConditionsEditor'
import { VerificationConditionsPreview } from './VerificationConditionsPreview'
import { VerificationConditionsRender } from './VerificationConditionsRender'
// Форматирование даты как в функции ДАТАПРОТОКОЛА
const formatVerificationDate = (dateStr: string): string => {
try {
const months = [

View File

@@ -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<string, ExcelFunction> {
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

View File

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

View File

@@ -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<string, CellData[][]> = {}
// 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<string, Record<string, any>>) => {
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,

View File

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

View File

@@ -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<Workbook | null>(null)
const [cells, setCells] = useState<CellData[][]>(() => {
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,
}
}