From aa18a8e19c05a8a89fee8d90a6caaa09def7fd7c Mon Sep 17 00:00:00 2001 From: PotapovaA Date: Mon, 22 Jun 2026 17:16:29 +0300 Subject: [PATCH] =?UTF-8?q?web=20=D1=81=D0=BE=D0=BA=D0=B5=D1=82=D1=8B=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D0=BD=D0=B0=D1=87=D0=B0=D0=BB=D0=B0/?= =?UTF-8?q?=D0=BA=D0=BE=D0=BD=D1=86=D0=B0=20=D1=80=D0=B5=D0=B4=D0=B0=D0=BA?= =?UTF-8?q?=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D1=8F?= =?UTF-8?q?=D1=87=D0=B5=D0=B9=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RealtimeTable/RealtimeTable.jsx | 36 ++++- .../RealtimeTable/contexts/RealtimeContext.js | 109 ++++++++++++++- .../RealtimeTable/hooks/useRealtimeData.js | 124 +++++++++++++++++- .../components/RealtimeTable/tableColumns.jsx | 11 +- 4 files changed, 261 insertions(+), 19 deletions(-) diff --git a/web/src/components/RealtimeTable/RealtimeTable.jsx b/web/src/components/RealtimeTable/RealtimeTable.jsx index 6af3d91..51f678d 100644 --- a/web/src/components/RealtimeTable/RealtimeTable.jsx +++ b/web/src/components/RealtimeTable/RealtimeTable.jsx @@ -25,13 +25,14 @@ import { toast } from 'react-toastify'; import { CircularProgress } from '@mui/material'; const RealtimeTable = ({ formType, formId, sheetName, direction }) => { - const { data, setData, isLoading: isTableLoading } = useRealtimeData(formId, sheetName, direction); + const { data, setData, isLoading: isTableLoading, editingCells: dataEditingCells } = useRealtimeData(formId, sheetName, direction); const [globalFilter, setGlobalFilter] = useState(''); const [showColumnFilters, setShowColumnFilters] = useState(false); const [selectedColumn, setSelectedColumn] = useState(); const [rowSelection, setRowSelection] = useState({}); const [isLoadingData, setIsLoadingData] = useState(false); const [isPending, startTransition] = useTransition(); + const [editingCell, setEditingCell] = useState(null); const handleGlobalFilterChange = (value) => { startTransition(() => { @@ -42,13 +43,14 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => { const { isConnected, isAuthenticated, - subscribeToCellUpdates, subscribeToErrors, subscribeToAuthSuccess, error: wsError, updateCell: contextUpdateCell, addRow: contextAddRow, deleteRow: contextDeleteRow, + startEditing: contextStartEditing, + endEditing: contextEndEditing, } = useRealtime(); const { @@ -144,6 +146,14 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => { scrollPaddingStart: 0, scrollPaddingEnd: 0, }, + onEditingCellChange: (cell) => { + console.log(cell, 'CELL') + if (cell) { + contextStartEditing?.(cell.row, cell.column); + } else { + contextEndEditing?.(editingCell.row, editingCell.column); + } + }, columnVirtualizerOptions: ({ table }) => ({ overscan: 5, measureElement: (el) => { @@ -163,6 +173,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => { globalFilter, showColumnFilters, rowSelection, + editingCell, }, initialState: { expanded: true, @@ -214,6 +225,27 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => { const table = useMaterialReactTable(tableConfig); + useEffect(() => { + if (!dataEditingCells.line_id) { + setEditingCell(null); + return; + } + if (editingCell) return; + const rowId = dataEditingCells.line_id; + const columnId = dataEditingCells.column; + try { + const row = table.getRow(rowId); + const column = table.getColumn(columnId); + const cell = row?.getVisibleCells().find(c => c.column.id === columnId); + if (cell) { + setEditingCell(cell); + } + } catch (error) { + console.error(error); + toast.error('Ошибка редактирования ячейки'); + } + }, [dataEditingCells, table]); + useEffect(() => { if (table && data && columns.length !== 0) { setIsLoadingData(true) diff --git a/web/src/components/RealtimeTable/contexts/RealtimeContext.js b/web/src/components/RealtimeTable/contexts/RealtimeContext.js index 3d1248b..b9ba118 100644 --- a/web/src/components/RealtimeTable/contexts/RealtimeContext.js +++ b/web/src/components/RealtimeTable/contexts/RealtimeContext.js @@ -28,7 +28,8 @@ export const RealtimeProvider = ({ userId, }) => { // Формируем URL (кастомные секреты на фронте пока недоступны, делаем через REACT_APP_ROOT_PATH) - const wsUrl = `${(process.env.REACT_APP_API_URL || process.env.REACT_APP_API_URL || process.env.REACT_APP_ROOT_PATH || "ws://localhost:8000").replace(/^http/, "ws")}/api/v1/ws/form/${formId}/sheet/${sheetName}${direction ? `?direction=${direction}` : ""}`; console.log() + const wsUrl = `${(process.env.REACT_APP_API_URL || process.env.REACT_APP_API_URL || process.env.REACT_APP_ROOT_PATH || "ws://localhost:8000").replace(/^http/, "ws")}/api/v1/ws/form/${formId}/sheet/${sheetName}${direction ? `?direction=${direction}` : ""}`; + const handleMessage = useCallback((data) => { // Обработка ошибок if (data.error) { @@ -38,8 +39,21 @@ export const RealtimeProvider = ({ } switch (data.event) { + case "cell_edit_start": + console.log("Cell edit started:", data); + if (onCellEditStartRef.current) { + onCellEditStartRef.current(data); + } + break; + + case "cell_edit_end": + console.log("Cell edit ended:", data); + if ( onCellEditEndRef.current) { + onCellEditEndRef.current(data); + } + break; + case "cell_updated": - console.log(onCellUpdateRef.current); if (data.result && onCellUpdateRef.current) { const updatedCells = Array.isArray(data.result) ? data.result @@ -82,6 +96,8 @@ export const RealtimeProvider = ({ const onRowDeleteRef = useRef(null); const onErrorRef = useRef(null); const onAuthSuccessRef = useRef(null); + const onCellEditStartRef = useRef(null); + const onCellEditEndRef = useRef(null); const authAttemptedRef = useRef(false); const reconnectTimerRef = useRef(null); @@ -134,6 +150,70 @@ export const RealtimeProvider = ({ } }, [isConnected]); + // Функция для начала редактирования ячейки + const startEditing = useCallback( + async (row, column) => { + const columnId = column.id; + const rowId = row.id; + + if (!isConnected) { + onErrorRef.current?.("WebSocket не подключен"); + return false; + } + + const lineId = row.id || null; + const colId = columnId.slice(5); // убираем префикс "data." + + const message = { + event: "cell_edit_start", + data: { + line_id: lineId, + column: colId, + }, + }; + + const sent = sendMessage(message); + if (!sent) { + return false; + } + + return true; + }, + [isConnected, sendMessage], + ); + + // Функция для завершения редактирования ячейки + const endEditing = useCallback( + async (row, column) => { + const columnId = column.id; + const rowId = row.id; + + if (!isConnected) { + onErrorRef.current?.("WebSocket не подключен"); + return false; + } + + const lineId = row.id || null; + const colId = columnId.slice(5); + + const message = { + event: "cell_edit_end", + data: { + line_id: lineId, + column: colId, + }, + }; + + const sent = sendMessage(message); + if (!sent) { + return false; + } + + return true; + }, + [isConnected, sendMessage], + ); + const updateCell = useCallback( async (row, column, value) => { const columnId = column.id; @@ -180,7 +260,7 @@ export const RealtimeProvider = ({ }; return sendMessage(message); }, - [isConnected, sendMessage], + [isConnected, sendMessage, sheetName, formId, direction], ); const deleteRow = useCallback( @@ -200,7 +280,7 @@ export const RealtimeProvider = ({ return sendMessage(message); }, - [isConnected, sendMessage], + [isConnected, sendMessage, sheetName, formId, direction], ); const subscribeToCellUpdates = useCallback((callback) => { @@ -238,6 +318,21 @@ export const RealtimeProvider = ({ }; }, []); + const subscribeToCellEditStart = useCallback((callback) => { + console.log('edit') + onCellEditStartRef.current = callback; + return () => { + onCellEditStartRef.current = null; + }; + }, []); + + const subscribeToCellEditEnd = useCallback((callback) => { + onCellEditEndRef.current = callback; + return () => { + onCellEditEndRef.current = null; + }; + }, []); + const retryLogin = useCallback(() => { if (isConnected) { authAttemptedRef.current = false; @@ -250,6 +345,8 @@ export const RealtimeProvider = ({ value={{ isConnected, error: lastError, + startEditing, + endEditing, updateCell, addRow, deleteRow, @@ -262,6 +359,8 @@ export const RealtimeProvider = ({ subscribeToRowDeletes, subscribeToErrors, subscribeToAuthSuccess, + subscribeToCellEditStart, + subscribeToCellEditEnd, retryLogin, releaseAllLocks: () => releaseAllLocks(userId), }} @@ -269,4 +368,4 @@ export const RealtimeProvider = ({ {children} ); -}; +}; \ No newline at end of file diff --git a/web/src/components/RealtimeTable/hooks/useRealtimeData.js b/web/src/components/RealtimeTable/hooks/useRealtimeData.js index 247b449..4aa9ac7 100644 --- a/web/src/components/RealtimeTable/hooks/useRealtimeData.js +++ b/web/src/components/RealtimeTable/hooks/useRealtimeData.js @@ -8,6 +8,7 @@ const useRealtimeData = (formId, sheetName, direction) => { const [isConnected, setIsConnected] = useState(false); const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(true); + const [editingCells, setEditingCells] = useState({}); const dataRef = useRef(data); const { @@ -17,9 +18,10 @@ const useRealtimeData = (formId, sheetName, direction) => { subscribeToRowAdds, subscribeToRowDeletes, subscribeToErrors, + subscribeToCellEditStart, + subscribeToCellEditEnd, } = useRealtime(); - // Ваша существующая функция форматирования const formatedToSubRows = useCallback((depth, oldData) => { if (depth === 0) return oldData; @@ -204,6 +206,100 @@ const useRealtimeData = (formId, sheetName, direction) => { }); }); + // Обработчик начала редактирования ячейки + const unsubscribeCellEditStart = subscribeToCellEditStart((data) => { + console.log(data); + console.log('START EDIT'); + const dataCell = data.data; + dataCell.column = 'data.'+ dataCell.column; + setEditingCells(data.data) + + // const { line_id, column } = data.data; + // const cellKey = `${line_id}_${column}`; + + // // Сохраняем информацию о том, кто начал редактирование + // setEditingCells((prev) => ({ + // ...prev, + // [cellKey]: { + // userId: data.user_id || "unknown", + // timestamp: Date.now(), + // }, + // })); + + // // Можно также добавить визуальную индикацию в данных + // setData((prevData) => { + // // Функция для обновления статуса редактирования в данных + // const updateCellEditingStatus = (items) => { + // return items.map((item) => { + // if (item.line_id === line_id) { + // // Находим нужную колонку и добавляем флаг редактирования + // const updatedItem = { ...item }; + // if (updatedItem.columns && updatedItem.columns[column]) { + // updatedItem.columns[column] = { + // ...updatedItem.columns[column], + // isEditing: true, + // editingBy: data.user_id, + // }; + // } + // return updatedItem; + // } + // if (item.subRows) { + // return { + // ...item, + // subRows: updateCellEditingStatus(item.subRows), + // }; + // } + // return item; + // }); + // }; + // return updateCellEditingStatus(prevData); + // }); + + // console.log(`Cell ${cellKey} editing started by user ${data.user_id}`); + }); + + // Обработчик завершения редактирования ячейки + const unsubscribeCellEditEnd = subscribeToCellEditEnd((data) => { + setEditingCells(null); + const { line_id, column } = data.data; + const cellKey = `${line_id}_${column}`; + + // Удаляем информацию о редактировании + setEditingCells((prev) => { + const newState = { ...prev }; + delete newState[cellKey]; + return newState; + }); + + // Убираем визуальную индикацию в данных + setData((prevData) => { + const updateCellEditingStatus = (items) => { + return items.map((item) => { + if (item.line_id === line_id) { + const updatedItem = { ...item }; + if (updatedItem.columns && updatedItem.columns[column]) { + // Удаляем флаги редактирования + const { isEditing, editingBy, ...restColumns } = + updatedItem.columns[column]; + updatedItem.columns[column] = restColumns; + } + return updatedItem; + } + if (item.subRows) { + return { + ...item, + subRows: updateCellEditingStatus(item.subRows), + }; + } + return item; + }); + }; + return updateCellEditingStatus(prevData); + }); + + console.log(`Cell ${cellKey} editing ended`); + }); + const unsubscribeErrors = subscribeToErrors((err) => { console.error("WebSocket error:", err); setError(err); @@ -215,6 +311,8 @@ const useRealtimeData = (formId, sheetName, direction) => { unsubscribeRowAdds(); unsubscribeRowDeletes(); unsubscribeErrors(); + unsubscribeCellEditStart(); + unsubscribeCellEditEnd(); }; }, [ wsConnected, @@ -222,6 +320,8 @@ const useRealtimeData = (formId, sheetName, direction) => { subscribeToRowAdds, subscribeToRowDeletes, subscribeToErrors, + subscribeToCellEditStart, + subscribeToCellEditEnd, formatedToSubRows, ]); @@ -244,13 +344,33 @@ const useRealtimeData = (formId, sheetName, direction) => { [formatedToSubRows], ); + // Функция для проверки, редактируется ли ячейка + const isCellEditing = useCallback( + (lineId, column) => { + const cellKey = `${lineId}_${column}`; + return !!editingCells[cellKey]; + }, + [editingCells], + ); + + // Функция для получения информации о том, кто редактирует ячейку + const getCellEditingInfo = useCallback( + (lineId, column) => { + const cellKey = `${lineId}_${column}`; + return editingCells[cellKey] || null; + }, + [editingCells], + ); + return { data, setData, - // setData: updateData, isConnected, isLoading, error, + editingCells, + isCellEditing, + getCellEditingInfo, }; }; diff --git a/web/src/components/RealtimeTable/tableColumns.jsx b/web/src/components/RealtimeTable/tableColumns.jsx index 9b38ebd..a47d5ce 100644 --- a/web/src/components/RealtimeTable/tableColumns.jsx +++ b/web/src/components/RealtimeTable/tableColumns.jsx @@ -97,22 +97,13 @@ export const getTableColumns = ({ EditCell, columnsConfig, onCellUpdateError, - onCellNumberClick + onCellNumberClick, }) => { const columnColors = { ...columnsConfig.colors }; const columns = [...columnsConfig.columns]; const processColumns = (columns, EditCell, Cell) => { columns.forEach((col) => { - if (col.header.length > 15) { - col.size = 250; - } - if(col.header.length >= 20){ - col.size = 350; - } - if(col.header.length >= 30){ - col.size = 400; - } col.Cell = ({ cell, table, column, row }) => (