From d347023211eb5df70df0245062b0ab100e580d25 Mon Sep 17 00:00:00 2001 From: PotapovaA Date: Wed, 19 Aug 2026 18:26:29 +0300 Subject: [PATCH] fix --- .../RealtimeTable/RealtimeTable.jsx | 18 ++--- .../SettingPanel/SettingPanel.jsx | 9 ++- .../RealtimeTable/contexts/RealtimeContext.js | 76 +++++++++++++++++-- .../RealtimeTable/hooks/useColumnSettings.js | 5 ++ 4 files changed, 86 insertions(+), 22 deletions(-) diff --git a/web/src/components/RealtimeTable/RealtimeTable.jsx b/web/src/components/RealtimeTable/RealtimeTable.jsx index 5e3ed64..97d2945 100644 --- a/web/src/components/RealtimeTable/RealtimeTable.jsx +++ b/web/src/components/RealtimeTable/RealtimeTable.jsx @@ -163,6 +163,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => { handleUnpinColumn, handleSetColumnWidth, handleToggleColumnVisibility, + handleSetColumnsVisibility, } = useColumnSettings(`${formId}_${formType}_${sheetName}_${direction}`); const { headerPortalRef, containerRef } = useHeaderPortal(); @@ -174,6 +175,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => { useEffect(() => { rowVirtualizerRef.current?.measure?.(); + columnVirtualizerRef.current?.measure?.(); }, [sizeMult]); useEffect(() => { @@ -300,10 +302,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => { }, [columns, columnPinning, columnSizing, columnVisibility]); const selectedColumnIdRef = useRef(selectedColumnId); - - useEffect(() => { - selectedColumnIdRef.current = selectedColumnId; - }, [selectedColumnId]); + selectedColumnIdRef.current = selectedColumnId; const tableMeta = useMemo(() => ({ updateCell: handleUpdateCell, @@ -610,13 +609,13 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => { return ( <> { onAddProgram={addProgram} onAddProject={addProject} onDeleteRow={handleDeleteRow} - isLoadingData={isLoadingData} formId={formId} sheetName={sheetName} direction={direction} diff --git a/web/src/components/RealtimeTable/SettingPanel/SettingPanel.jsx b/web/src/components/RealtimeTable/SettingPanel/SettingPanel.jsx index 1851c34..6647e02 100644 --- a/web/src/components/RealtimeTable/SettingPanel/SettingPanel.jsx +++ b/web/src/components/RealtimeTable/SettingPanel/SettingPanel.jsx @@ -37,11 +37,11 @@ const exportButtonSx = { height: '2.5rem', minHeight: '2.5rem' }; const SettingPanel = ({ selectedColumnId, columnPinning, - table, columns, onPinColumn, onUnpinColumn, onToggleColumnVisibility, + onSetColumnsVisibility, columnVisibility, columnsCurStage, onChangeSizeMult, @@ -53,7 +53,6 @@ const SettingPanel = ({ onAddProgram, onAddProject, onDeleteRow, - isLoadingData, formId, sheetName, direction, @@ -119,10 +118,12 @@ const SettingPanel = ({ console.warn('Нет столбцов для текущего этапа'); return; } + const visibilityByColumnId = {}; for (const column of leafColumns) { - onToggleColumnVisibility(column, showOnlyCurrentStageColumns || stageColumnIds.has(column.id)); + visibilityByColumnId[column.id] = showOnlyCurrentStageColumns || stageColumnIds.has(column.id); } - }, [columnsCurStage, leafColumns, onToggleColumnVisibility, showOnlyCurrentStageColumns, stageColumnIds]); + onSetColumnsVisibility(visibilityByColumnId); + }, [columnsCurStage, leafColumns, onSetColumnsVisibility, showOnlyCurrentStageColumns, stageColumnIds]); return ( <> diff --git a/web/src/components/RealtimeTable/contexts/RealtimeContext.js b/web/src/components/RealtimeTable/contexts/RealtimeContext.js index 7850f9c..397258e 100644 --- a/web/src/components/RealtimeTable/contexts/RealtimeContext.js +++ b/web/src/components/RealtimeTable/contexts/RealtimeContext.js @@ -3,21 +3,79 @@ import { useCallback, useContext, useEffect, + useMemo, useRef, useState, + useSyncExternalStore, } from "react"; import { toast } from "react-toastify"; import { useWebSocket } from "../hooks/useWebSocket"; import { getRowId } from "../utils/rowUtils"; -const RealtimeContext = createContext(null); +const RealtimeActionsContext = createContext(null); +const RealtimeConnectionContext = createContext(null); +const RealtimeVspOptionsContext = createContext(null); +const LockedCellsStoreContext = createContext(null); + +const useRequiredContext = (context, hookName) => { + const value = useContext(context); + if (value === null) { + throw new Error(`${hookName} must be used within RealtimeProvider`); + } + return value; +}; + +const createLockedCellsStore = () => { + let cells = new Set(); + const listeners = new Set(); + + const subscribe = (listener) => { + listeners.add(listener); + return () => listeners.delete(listener); + }; + + const setCells = (nextValue) => { + const currentCells = [...cells]; + const resolvedValue = typeof nextValue === "function" ? nextValue(currentCells) : nextValue; + const nextCells = new Set(resolvedValue || []); + const hasChanges = + nextCells.size !== cells.size || [...nextCells].some((cellKey) => !cells.has(cellKey)); + + if (!hasChanges) return; + cells = nextCells; + listeners.forEach((listener) => listener()); + }; + + return { + subscribe, + setCells, + hasCell: (cellKey) => cells.has(cellKey), + getCells: () => [...cells], + }; +}; + +export const useRealtimeActions = () => + useRequiredContext(RealtimeActionsContext, "useRealtimeActions"); + +export const useRealtimeConnection = () => + useRequiredContext(RealtimeConnectionContext, "useRealtimeConnection"); + +export const useVspOptions = () => + useRequiredContext(RealtimeVspOptionsContext, "useVspOptions"); + +export const useCellLock = (cellKey) => { + const store = useRequiredContext(LockedCellsStoreContext, "useCellLock"); + const getSnapshot = useCallback(() => store.hasCell(cellKey), [cellKey, store]); + + return useSyncExternalStore(store.subscribe, getSnapshot, () => false); +}; export const useRealtime = () => { - const context = useContext(RealtimeContext); - if (!context) { - throw new Error("useRealtime must be used within RealtimeProvider"); - } - return context; + const actions = useRealtimeActions(); + const connection = useRealtimeConnection(); + const vspOptions = useVspOptions(); + + return { ...actions, ...connection, vspOptions }; }; export const RealtimeProvider = ({ @@ -41,9 +99,11 @@ export const RealtimeProvider = ({ : `/api/v1/ws/projects/${formId}/report/${year}/${sheetName}` }`; - //для ячеек которые редактируются другими пользователями - const [lockedCells, setLockedCells] = useState([]); const [vspOptions, setVspOptions] = useState([]); + const lockedCellsStoreRef = useRef(null); + if (lockedCellsStoreRef.current === null) { + lockedCellsStoreRef.current = createLockedCellsStore(); + } const handleMessage = useCallback((data) => { // Обработка ошибок diff --git a/web/src/components/RealtimeTable/hooks/useColumnSettings.js b/web/src/components/RealtimeTable/hooks/useColumnSettings.js index adc2640..a6c6741 100644 --- a/web/src/components/RealtimeTable/hooks/useColumnSettings.js +++ b/web/src/components/RealtimeTable/hooks/useColumnSettings.js @@ -66,6 +66,10 @@ export const useColumnSettings = (tableKey) => { setColumnVisibility((prev) => ({ ...prev, ...leafCols })); }, []); + const handleSetColumnsVisibility = useCallback((visibilityByColumnId) => { + setColumnVisibility((prev) => ({ ...prev, ...visibilityByColumnId })); + }, []); + // Опционально: функция для сброса всех настроек const resetAllSettings = useCallback(() => { setColumnSizing({}); @@ -84,6 +88,7 @@ export const useColumnSettings = (tableKey) => { handleUnpinColumn, handleSetColumnWidth, handleToggleColumnVisibility, + handleSetColumnsVisibility, resetAllSettings, // добавляем функцию сброса tableKey, // опционально возвращаем tableKey };