diff --git a/web/src/components/RealtimeTable/Cell/Cell/Cell.jsx b/web/src/components/RealtimeTable/Cell/Cell/Cell.jsx
index 1478848..7212e7f 100644
--- a/web/src/components/RealtimeTable/Cell/Cell/Cell.jsx
+++ b/web/src/components/RealtimeTable/Cell/Cell/Cell.jsx
@@ -1,7 +1,8 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import { useMemo } from 'react';
import styled from '@emotion/styled';
import { useRealtime } from '../../contexts/RealtimeContext';
+import { toast } from 'react-toastify';
const ContainerForText = styled.span`
@@ -42,7 +43,7 @@ const highlightText = (text, searchQuery) => {
);
};
-const Cell = React.memo(({ cell, row, column, onClick, globalFilter, backgroundColor, color }) => {
+const Cell = React.memo(({ cell, row, column, onClick, globalFilter, backgroundColor, color, isEditable }) => {
const cellValue = cell.getValue();
const textValue = String(cellValue || '');
@@ -51,14 +52,11 @@ const Cell = React.memo(({ cell, row, column, onClick, globalFilter, backgroundC
return highlightText(textValue, globalFilter);
}, [textValue, globalFilter]);
- const {
- isCellLocked,
- getCellLockInfo,
- updateCell,
- subscribeToErrors,
- tryLockCell,
- unlockCell
- } = useRealtime();
+ const lockedStyles = !isEditable ? {
+ border: '2px solid #d3d3d3',
+ color: '#525252',
+ cursor: 'not-allowed',
+ } : {};
return (
@@ -80,6 +78,7 @@ const Cell = React.memo(({ cell, row, column, onClick, globalFilter, backgroundC
padding: '8px',
backgroundColor: backgroundColor,
color: color,
+ ...lockedStyles
}}
onClick={onClick}
>
diff --git a/web/src/components/RealtimeTable/Cell/EditCell/EditCell.jsx b/web/src/components/RealtimeTable/Cell/EditCell/EditCell.jsx
index ec686de..68a87d3 100644
--- a/web/src/components/RealtimeTable/Cell/EditCell/EditCell.jsx
+++ b/web/src/components/RealtimeTable/Cell/EditCell/EditCell.jsx
@@ -7,7 +7,7 @@ const EditCell = ({ refCell, onChange, disabled, value }) => {
const left = tdFrag.offsetLeft;
const width = refCell.current.offsetParent.offsetWidth;
- const translateY = 'translateY(' + trFrag.offsetTop + 'px)';
+ const translateY = trFrag.style.transform;
function useAutoResize(value) {
const ref = useRef(null);
@@ -71,7 +71,7 @@ const EditCell = ({ refCell, onChange, disabled, value }) => {
}}
onChange={handleChangeValue}
value={curValue}
- onBlur={handleChange}
+ onBlur={handleChange}
/>
diff --git a/web/src/components/RealtimeTable/RealtimeTable.jsx b/web/src/components/RealtimeTable/RealtimeTable.jsx
index 655c333..39e6f0b 100644
--- a/web/src/components/RealtimeTable/RealtimeTable.jsx
+++ b/web/src/components/RealtimeTable/RealtimeTable.jsx
@@ -1,5 +1,5 @@
// components/RealtimeTable/index.js
-import React, { useState, useMemo, useRef, useEffect, useCallback } from 'react';
+import React, { useState, useMemo, useRef, useEffect, useCallback, useTransition } from 'react';
import { createPortal } from 'react-dom';
import {
useMaterialReactTable,
@@ -25,12 +25,18 @@ import { toast } from 'react-toastify';
const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
const { data, setData } = useRealtimeData(formId, sheetName, direction);
- const [isLoading, setIsLoading] = useState(true);
const [globalFilter, setGlobalFilter] = useState('');
const [showColumnFilters, setShowColumnFilters] = useState(false);
const [selectedColumn, setSelectedColumn] = useState();
- const columnVirtualizerInstanceRef = useRef(null);
const [rowSelection, setRowSelection] = useState({});
+ const [isLoadingData, setIsLoadingData] = useState(false);
+ const [isPending, startTransition] = useTransition();
+
+ const handleGlobalFilterChange = (value) => {
+ startTransition(() => {
+ setGlobalFilter(value);
+ });
+ };
const {
isConnected,
@@ -85,7 +91,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
setRowSelection((prev) => ({
[rowId]: !prev[rowId],
}));
- }, [rowSelection])
+ }, [])
const columns = useMemo(() => {
@@ -108,7 +114,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
}
}, [columnsConfig, handleUpdateCell]);
-
const tableMeta = useMemo(() => ({
updateCell: handleUpdateCell,
}), [handleUpdateCell]);
@@ -119,13 +124,17 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
...BASE_TABLE_CONFIG,
columns,
data: data,
- columnVirtualizerInstanceRef,
- enableRowVirtualization: false,
+ enableRowVirtualization: true,
+ rowVirtualizerOptions: {
+ overscan: 50,
+ scrollPaddingStart: 0,
+ scrollPaddingEnd: 0,
+ },
enableRowSelection: true,
onRowSelectionChange: setRowSelection,
onColumnSizingChange: setColumnSizing,
onColumnPinningChange: setColumnPinning,
- onGlobalFilterChange: setGlobalFilter,
+ onGlobalFilterChange: handleGlobalFilterChange,
state: {
columnSizing,
columnPinning,
@@ -135,7 +144,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
rowSelection
},
initialState: {
- hiddenColumn: []
+ expanded: true
},
meta: tableMeta,
muiTableHeadCellProps: ({ column }) => ({
@@ -153,22 +162,13 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
height: '1px',
minHeight: '1px',
maxHeight: '1px',
- opacity: 0,
- pointerEvents: 'none',
- backgroundColor: 'transparent',
- '& th': {
- height: '1px',
- minHeight: '1px',
- maxHeight: '1px',
- },
+ visibility: 'hidden',
},
},
muiTablePaperProps: getTablePaperStyles(),
muiTableContainerProps: {
ref: containerRef,
sx: {
- overflowX: 'auto',
- overflowY: 'auto',
position: 'relative',
contain: 'layout',
minHeight: '100%',
@@ -198,6 +198,12 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
const table = useMaterialReactTable(tableConfig);
+ useEffect(() => {
+ if (table && data && columns.length !== 0) {
+ setIsLoadingData(true)
+ }
+ }, [data, columns, table])
+
const handleAddRow = useCallback(() => {
const allRows = table.getRowModel().rows;
@@ -222,7 +228,9 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
const row = selectedRows[0];
const expense_item_id = row.original.data.line_id;
contextDeleteRow(expense_item_id)
+ setRowSelection({});
}, [rowSelection, table])
+
if (!data) {
return
Загрузка...
;
}
@@ -237,11 +245,12 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
onToggleColumnVisibility={handleToggleColumnVisibility}
columnVisibility={columnVisibility}
onChangeSizeMult={setSizeMult}
- onGlobalFilterChange={setGlobalFilter}
+ onGlobalFilterChange={handleGlobalFilterChange}
sizeMult={sizeMult}
onChangeShowColumnFilters={setShowColumnFilters}
onAddRow={handleAddRow}
onDeleteRow={handleDeleteRow}
+ isLoadingData={isLoadingData}
/>
@@ -265,6 +274,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
onHeaderSelect={setSelectedColumn}
selectHeader={selectedColumn}
onChangeWidth={handleSetColumnWidth}
+ isLoadingData={isLoadingData}
/>,
headerPortalRef.current,
)}
diff --git a/web/src/components/RealtimeTable/SettingPanel/SettingPanel.jsx b/web/src/components/RealtimeTable/SettingPanel/SettingPanel.jsx
index 494f694..6477c84 100644
--- a/web/src/components/RealtimeTable/SettingPanel/SettingPanel.jsx
+++ b/web/src/components/RealtimeTable/SettingPanel/SettingPanel.jsx
@@ -31,6 +31,7 @@ const SettingPanel = ({
onChangeShowColumnFilters,
onAddRow,
onDeleteRow,
+ isLoadingData,
}) => {
const [isPinned, setIsPinned] = useState(false);
const [columnTree, setColumnTree] = useState();
@@ -41,7 +42,7 @@ const SettingPanel = ({
if (!table) return;
setColumnTree(table.getAllColumns());
setShowColumnFilters(table.getState().showColumnFilters);
- }, [table]);
+ }, [table, isLoadingData]);
useEffect(() => {
if (!selectedColumn) return;
diff --git a/web/src/components/RealtimeTable/TableHead/TableHead.jsx b/web/src/components/RealtimeTable/TableHead/TableHead.jsx
index fe15262..45ba813 100644
--- a/web/src/components/RealtimeTable/TableHead/TableHead.jsx
+++ b/web/src/components/RealtimeTable/TableHead/TableHead.jsx
@@ -131,7 +131,6 @@ const HeaderCell = ({ header, table, onClick, hightlight, onChangeWidth }) => {
// Если колонка не закреплена, текст должен начинаться после всех закрепленных колонок
const getTextLeftOffset = () => {
if (isPinned) {
- // Для закрепленных колонок текст остается на месте
return '0';
}
// Для незакрепленных колонок текст должен начинаться после всех закрепленных
@@ -215,7 +214,7 @@ const FilterRow = ({ table }) => {
style={{
display: 'flex',
position: 'sticky',
- top: '1.5rem', // После строки с номерами колонок
+ top: '1.5rem',
zIndex: 20,
backgroundColor: 'white',
}}
@@ -338,11 +337,16 @@ export const TableHead = ({
onHeaderSelect,
selectHeader,
onChangeWidth,
+ isLoadingData,
}) => {
const handleHeaderClick = (header) => {
onHeaderSelect(header);
};
+ if (!isLoadingData) {
+ return
+ }
+
return (
<>
{
return;
}
- const maxDepth = data.reduce(
- (prev, current) => {
- return (prev.depth || 0) > (current.depth || 0) ? prev : current;
- },
- { depth: 0 },
- ).depth;
-
- const formattedData = formatedToSubRows(maxDepth, data);
- setData(formattedData);
+ const hierarchicalData = buildHierarchy(data);
+ setData(hierarchicalData);
} catch (err) {
console.error("Failed to load initial data:", err);
setError(err.message);
@@ -104,7 +97,54 @@ const useRealtimeData = (formId, sheetName, direction) => {
};
loadInitialData();
- }, [formId, sheetName, formatedToSubRows]);
+ }, [formId, sheetName]);
+
+ const buildHierarchy = (items) => {
+ const result = [];
+ const stack = [];
+
+ const getTypeLevel = (type) => {
+ const levels = {
+ ROOT: 0,
+ GROUP: 1,
+ ITEM: 2,
+ SUB_ITEM: 3,
+ INPUT: 4,
+ };
+ return levels[type] ?? 999;
+ };
+
+ for (const item of items) {
+ const currentLevel = getTypeLevel(item.row_type);
+
+ while (
+ stack.length > 0 &&
+ getTypeLevel(stack[stack.length - 1].row_type) >= currentLevel
+ ) {
+ stack.pop();
+ }
+
+ const parent = stack.length > 0 ? stack[stack.length - 1] : null;
+
+ const newNode = { ...item, subRows: [] };
+
+ if (parent) {
+ if (!parent.subRows) {
+ parent.subRows = [];
+ }
+ parent.subRows.push(newNode);
+ } else {
+ result.push(newNode);
+ }
+
+ // Добавляем текущий узел в стек, если он может иметь детей
+ if (item.row_type !== "INPUT") {
+ stack.push(newNode);
+ }
+ }
+
+ return result;
+ };
useEffect(() => {
setIsConnected(wsConnected);
diff --git a/web/src/components/RealtimeTable/tableColumns.jsx b/web/src/components/RealtimeTable/tableColumns.jsx
index d6fc397..8c9af2a 100644
--- a/web/src/components/RealtimeTable/tableColumns.jsx
+++ b/web/src/components/RealtimeTable/tableColumns.jsx
@@ -9,7 +9,8 @@ function EditCellPortal({
EditCell,
onSaveStart,
onSaveEnd,
- onError
+ onError,
+ isEditable
}) {
const ref = useRef(null);
const [refTbody, setRefTbody] = useState(false);
@@ -25,17 +26,7 @@ function EditCellPortal({
const handleChange = async (val) => {
table.setEditingCell(null);
- if (isSaving) return;
-
- setIsSaving(true);
- onSaveStart?.();
-
- // Сохраняем старое значение на случай ошибки
- const oldValue = cell.getValue();
-
try {
-
- // Используем updateCell из контекста (передается через meta)
if (table.options.meta?.updateCell) {
const success = await table.options.meta.updateCell(
cell.row,
@@ -44,32 +35,15 @@ function EditCellPortal({
);
if (success) {
console.log('Cell updated successfully via WebSocket');
- onSaveEnd?.(true);
- table.setEditingCell(null);
} else {
console.error('Failed to update cell via WebSocket');
const errorMsg = 'Не удалось сохранить изменение';
onError?.(errorMsg);
- onSaveEnd?.(false);
- // Возвращаем предыдущее значение
-
}
- } else {
- // Если updateCell нет в meta, используем onCellUpdate из пропсов (fallback)
- console.warn('WebSocket updateCell not configured in meta');
- if (table.options.meta?.onCellUpdate) {
- table.options.meta.onCellUpdate(cell.row.id, cell.column.id, val);
- }
- onSaveEnd?.(true);
- table.setEditingCell(null);
- }
+ }
} catch (error) {
console.error('Error updating cell:', error);
onError?.(error.message);
- onSaveEnd?.(false);
-
- } finally {
- setIsSaving(false);
}
};
@@ -88,7 +62,7 @@ function EditCellPortal({
refCell={ref}
cell={cell}
value={cell.getValue()}
- disabled={isDisabled || isSaving}
+ disabled={!isEditable}
onChange={handleChange}
/>,
refTbody,
@@ -131,8 +105,9 @@ export const getTableColumns = ({
column={column}
cell={cell}
globalFilter={
- table.getState().globalFilter || column.getFilterValue()
+ table.getState().globalFilter
}
+ isEditable={row.original?.row_type === 'INPUT' || false}
backgroundColor={columnColors[column.id]?.color_type?.[row.original?.row_type || row.row_type]}
color={columnColors[column.id]?.color_type?.['COLOR']}
isUpdating={table.options.meta?.updatingCells?.[`${row.id}_${column.id}`]}
@@ -144,20 +119,8 @@ export const getTableColumns = ({
cell={cell}
table={table}
EditCell={EditCell}
- onSaveStart={() => {
- if (table.options.meta?.setUpdatingCell) {
- table.options.meta.setUpdatingCell(row.id, column.id, true);
- }
- }}
- onSaveEnd={(success) => {
- if (table.options.meta?.setUpdatingCell) {
- table.options.meta.setUpdatingCell(row.id, column.id, false);
- }
- if (!success && onCellUpdateError) {
- onCellUpdateError(row.id, column.id, 'Failed to save');
- }
- }}
onError={onCellUpdateError}
+ isEditable={row.original?.row_type === 'INPUT' || false}
/>
);
@@ -177,10 +140,9 @@ export const getTableColumns = ({
enableEditing: false,
enablePinning: true,
Cell: ({ cell, row }) => {
- return
{console.log('TEST', row.id); onCellNumberClick(row.id)}}
- >{cell.getValue()};
+ return
},
};
diff --git a/web/src/components/RealtimeTable/utils/cellUtils.js b/web/src/components/RealtimeTable/utils/cellUtils.js
index 4753e9d..509286a 100644
--- a/web/src/components/RealtimeTable/utils/cellUtils.js
+++ b/web/src/components/RealtimeTable/utils/cellUtils.js
@@ -1,5 +1,4 @@
export function setNewValueToData(data, colId, rowId, value) {
- // 1. Находим целевой объект по rowId(индексы вложенных subRows)
let targetObject = data;
if (rowId && rowId.length) {
const indices = rowId.split(".").map(Number);
diff --git a/web/src/components/common/TableCard/TableCard.jsx b/web/src/components/common/TableCard/TableCard.jsx
index fa12a9d..d5e4223 100644
--- a/web/src/components/common/TableCard/TableCard.jsx
+++ b/web/src/components/common/TableCard/TableCard.jsx
@@ -14,7 +14,7 @@ const TableCard = ({ table, onNavigate }) => {
};
return (
-
+
diff --git a/web/src/components/common/TableList/TableList.jsx b/web/src/components/common/TableList/TableList.jsx
index 28be971..e0afa57 100644
--- a/web/src/components/common/TableList/TableList.jsx
+++ b/web/src/components/common/TableList/TableList.jsx
@@ -26,7 +26,7 @@ const TablesList = ({
return (
handleNavigate(path)}
table={table}
/>