Compare commits
No commits in common. "8c1341bba8c2deefc056f6312601c7c50c5f9b4e" and "e916f05ff2545204b92354fa40f6b6a2bd3a1d54" have entirely different histories.
8c1341bba8
...
e916f05ff2
@ -31,15 +31,6 @@ const INVALID_STYLES = {
|
||||
color: '#C60C0C',
|
||||
};
|
||||
|
||||
const EDITABLE_STYLES = {
|
||||
cursor: 'pointer',
|
||||
border: '2px solid #d8e4ea94',
|
||||
};
|
||||
|
||||
const NOT_EDITABLE_STYLES = {
|
||||
cursor: 'default',
|
||||
};
|
||||
|
||||
const LOCK_BADGE_STYLES = {
|
||||
position: 'absolute',
|
||||
top: '2px',
|
||||
@ -186,9 +177,8 @@ const CellComponent = ({
|
||||
color: textColor,
|
||||
...(isLocked ? LOCKED_STYLES : {}),
|
||||
...(isInvalid ? INVALID_STYLES : {}),
|
||||
...(isEditable ? EDITABLE_STYLES : NOT_EDITABLE_STYLES),
|
||||
}),
|
||||
[backgroundColor, isInvalid, isLocked, textColor, isEditable],
|
||||
[backgroundColor, isInvalid, isLocked, textColor],
|
||||
);
|
||||
|
||||
const lockBadge = useMemo(() => {
|
||||
@ -197,10 +187,10 @@ const CellComponent = ({
|
||||
}, [isLocked]);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
if (!isLocked && isEditable && onClick) {
|
||||
if (!isLocked && onClick) {
|
||||
onClick();
|
||||
}
|
||||
}, [isLocked, isEditable, onClick]);
|
||||
}, [isLocked, onClick]);
|
||||
|
||||
return (
|
||||
// biome-ignore lint/a11y/useKeyWithClickEvents: <explanation>
|
||||
|
||||
@ -34,7 +34,7 @@ import { debounce } from '@mui/material';
|
||||
import { CreateProgramModal } from './Modals/CreateProgramProjectModal';
|
||||
|
||||
const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
||||
const { data, setData, columnsCurStage, isLoading: isTableLoading, editingCells: dataEditingCells } = useRealtimeData(formId, sheetName, direction, formType, year);
|
||||
const { data, setData, isLoading: isTableLoading, editingCells: dataEditingCells } = useRealtimeData(formId, sheetName, direction, formType, year);
|
||||
const [globalFilter, setGlobalFilter] = useState('');
|
||||
const [showColumnFilters, setShowColumnFilters] = useState(false);
|
||||
const [selectedColumnId, setSelectedColumnId] = useState();
|
||||
@ -48,9 +48,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
||||
columnsConfig?.columns,
|
||||
);
|
||||
|
||||
const [expanded, setExpanded] = useState(true);
|
||||
const [showOnlyDepth2, setShowOnlyDepth2] = useState(false);
|
||||
|
||||
const [isOpenModalSelectVsp, setIsOpenModalSelectVsp] = useState(false);
|
||||
const [isOpenModalSelectExpenseItem, setIsOpenModalSelectExpenseItem] = useState(false);
|
||||
const [isOpenModalCreateProgram, setIsOpenModalCreateProgram] = useState(false);
|
||||
@ -177,13 +174,12 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
||||
}, []);
|
||||
|
||||
const columns = useMemo(() => {
|
||||
if (!columnsConfig || !columnsConfig.columns || columnsCurStage.length === 0) return [];
|
||||
if (!columnsConfig || !columnsConfig.columns) return [];
|
||||
try {
|
||||
return getTableColumns({
|
||||
Cell,
|
||||
EditCell,
|
||||
columnsConfig,
|
||||
columnsCurStage,
|
||||
onCellUpdate: handleUpdateCell,
|
||||
onCellNumberClick: handleClickRowCell,
|
||||
onCellUpdateError: handleCellUpdateError,
|
||||
@ -195,7 +191,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
||||
}
|
||||
}, [
|
||||
columnsConfig,
|
||||
columnsCurStage,
|
||||
handleCellUpdateError,
|
||||
handleClickRowCell,
|
||||
handleUpdateCell,
|
||||
@ -227,7 +222,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
||||
columnSizing,
|
||||
columnPinning,
|
||||
columnVisibility,
|
||||
expanded,
|
||||
globalFilter,
|
||||
showColumnFilters,
|
||||
rowSelection,
|
||||
@ -235,7 +229,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
||||
tableMeta,
|
||||
setColumnSizing,
|
||||
setColumnPinning,
|
||||
setExpanded,
|
||||
editingCell,
|
||||
contextStartEditing,
|
||||
}) => ({
|
||||
@ -270,7 +263,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
||||
onRowSelectionChange: setRowSelection,
|
||||
onColumnSizingChange: setColumnSizing,
|
||||
onColumnPinningChange: setColumnPinning,
|
||||
onExpandedChange: setExpanded,
|
||||
onGlobalFilterChange: handleGlobalFilterChange,
|
||||
state: {
|
||||
columnSizing,
|
||||
@ -280,7 +272,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
||||
showColumnFilters,
|
||||
rowSelection,
|
||||
editingCell,
|
||||
expanded
|
||||
},
|
||||
initialState: {
|
||||
expanded: true,
|
||||
@ -321,13 +312,13 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
||||
},
|
||||
});
|
||||
|
||||
// Конфигурация таблицы
|
||||
const tableConfig = useMemo(() => createTableConfig({
|
||||
columns,
|
||||
data,
|
||||
columnSizing,
|
||||
columnPinning,
|
||||
columnVisibility,
|
||||
expanded,
|
||||
globalFilter,
|
||||
showColumnFilters,
|
||||
rowSelection,
|
||||
@ -335,7 +326,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
||||
tableMeta,
|
||||
setColumnSizing,
|
||||
setColumnPinning,
|
||||
setExpanded,
|
||||
editingCell,
|
||||
contextStartEditing,
|
||||
}), [
|
||||
@ -352,7 +342,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
||||
setColumnSizing,
|
||||
setColumnPinning,
|
||||
editingCell,
|
||||
expanded,
|
||||
]);
|
||||
|
||||
const table = useMaterialReactTable(tableConfig);
|
||||
@ -391,6 +380,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
||||
}
|
||||
if (editingCell) return;
|
||||
|
||||
// Отложить поиск до следующего тика
|
||||
requestAnimationFrame(() => {
|
||||
try {
|
||||
const row = table.getRow(dataEditingCells.line_id);
|
||||
@ -491,12 +481,12 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
||||
return handleOpenModalSelectExpenseItem();
|
||||
}
|
||||
return handleAddRow();
|
||||
}, [isVspAdditionRow, isAdditionExpenseItem, handleOpenModalSelectVsp, handleAddRow, handleOpenModalSelectExpenseItem]);
|
||||
}, [isVspAdditionRow, isAdditionExpenseItem, handleOpenModalSelectVsp, handleAddRow]);
|
||||
|
||||
const addProgram = useCallback(() => {
|
||||
setIsProgramCreate(true);
|
||||
setIsOpenModalCreateProgram(true);
|
||||
}, [])
|
||||
}, [setIsOpenModalCreateProgram])
|
||||
|
||||
const addProject = useCallback(() => {
|
||||
setIsProgramCreate(false);
|
||||
@ -526,32 +516,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleToggleDepthVisibility = useCallback(() => {
|
||||
setShowOnlyDepth2(prev => {
|
||||
const newState = !prev;
|
||||
|
||||
if (newState) {
|
||||
const allRows = table.getRowModel().flatRows;
|
||||
console.log(allRows);
|
||||
const newExpanded = {};
|
||||
|
||||
for (const row of allRows) {
|
||||
const depth = row.original.depth || 0;
|
||||
|
||||
if (depth < 2 && row.getCanExpand()) {
|
||||
newExpanded[row.id] = true;
|
||||
}
|
||||
}
|
||||
|
||||
setExpanded(newExpanded);
|
||||
} else {
|
||||
setExpanded(true);
|
||||
}
|
||||
|
||||
return newState;
|
||||
});
|
||||
}, [table]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsPanel
|
||||
@ -563,7 +527,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
||||
onUnpinColumn={handleUnpinColumn}
|
||||
onToggleColumnVisibility={handleToggleColumnVisibility}
|
||||
columnVisibility={columnVisibility}
|
||||
columnsCurStage={columnsCurStage}
|
||||
onChangeSizeMult={setSizeMult}
|
||||
onGlobalFilterChange={handleGlobalFilterChange}
|
||||
sizeMult={sizeMult}
|
||||
@ -577,12 +540,10 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
||||
sheetName={sheetName}
|
||||
direction={direction}
|
||||
year={year}
|
||||
isProject={formType === 'PROJECT'}
|
||||
isProject={formType == 'PROJECT'}
|
||||
formType={formType}
|
||||
validationErrors={validationErrors}
|
||||
onNavigateToColumn={handleNavigateToColumn}
|
||||
showOnlyDepth2={showOnlyDepth2}
|
||||
onToggleDepthVisibility={handleToggleDepthVisibility}
|
||||
/>
|
||||
|
||||
<div style={tableWrapperStyle}>
|
||||
|
||||
@ -1,17 +1,15 @@
|
||||
import { Divider, IconButton, Stack, Tooltip } from '@mui/material';
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FORM_TYPE_OPTIONS } from '../../../constants/constants';
|
||||
import { exportSheet, exportSheetProject } from '../../../utils/exportFile';
|
||||
import { ExportDefaultButton } from '../../common/Buttons/ButtonsActions';
|
||||
import CollapsibleTree from '../../common/CollapsibleTree';
|
||||
import SearchComponent from '../../common/SearchComponent';
|
||||
import { AddLine, Eye, Minus, Pin, Plus, Program, Project, RemoveLine, Search } from '../../common/icons/icons';
|
||||
import { AddLine, Minus, Pin, Plus, Program, Project, RemoveLine, Search } from '../../common/icons/icons';
|
||||
import { GroupByObject } from './GroupByObject/GroupByObject';
|
||||
import { Panel } from './SettingPanel.style';
|
||||
import ValidationErrorsAlert from './ValidationErrorsAlert';
|
||||
import ZoomSlider from './ZoomSlider';
|
||||
import { collectLeafColumns } from '../utils/columnUtils';
|
||||
import { stageColumnsHiddenFromPicker } from '../../Stages/constants';
|
||||
|
||||
const SettingPanel = ({
|
||||
selectedColumnId,
|
||||
@ -22,7 +20,6 @@ const SettingPanel = ({
|
||||
onUnpinColumn,
|
||||
onToggleColumnVisibility,
|
||||
columnVisibility,
|
||||
columnsCurStage,
|
||||
onChangeSizeMult,
|
||||
onGlobalFilterChange,
|
||||
sizeMult,
|
||||
@ -40,8 +37,6 @@ const SettingPanel = ({
|
||||
formType,
|
||||
validationErrors = [],
|
||||
onNavigateToColumn,
|
||||
showOnlyDepth2,
|
||||
onToggleDepthVisibility,
|
||||
}) => {
|
||||
const [isPinned, setIsPinned] = useState(false);
|
||||
const [isExporting, setIsExporting] = useState(false);
|
||||
@ -86,20 +81,6 @@ const SettingPanel = ({
|
||||
await exportSheet(formId, sheetName, direction);
|
||||
setIsExporting(false);
|
||||
};
|
||||
// Обработчик скрытия всех столбцов кроме текущего этапа
|
||||
const handleShowOnlyCurrentStageColumns = useCallback(() => {
|
||||
if (!columnsCurStage || columnsCurStage.length === 0) {
|
||||
console.warn('Нет столбцов для текущего этапа');
|
||||
return;
|
||||
}
|
||||
//колонки которые не относятся не к какому этапу
|
||||
const allColumnVisible = [...stageColumnsHiddenFromPicker, ...columnsCurStage];
|
||||
const leafCols = collectLeafColumns({ columns: columns });
|
||||
for (const col of leafCols) {
|
||||
onToggleColumnVisibility(col, allColumnVisible.includes(col.id));
|
||||
}
|
||||
|
||||
}, [columnsCurStage, columns, columnVisibility, onToggleColumnVisibility]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -144,7 +125,7 @@ const SettingPanel = ({
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{/* проверяем на 4 форму */}
|
||||
{formType === FORM_TYPE_OPTIONS[3] && (
|
||||
{formType == FORM_TYPE_OPTIONS[3] && (
|
||||
<>
|
||||
<Tooltip title='Добавить программу'>
|
||||
<IconButton onClick={onAddProgram} variant='outlined'>
|
||||
@ -161,11 +142,6 @@ const SettingPanel = ({
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
<Tooltip title='Скрыть статьи расходов'>
|
||||
<IconButton onClick={onToggleDepthVisibility} variant='outlined'>
|
||||
<Eye />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</GroupByObject>
|
||||
</>
|
||||
)}
|
||||
@ -176,16 +152,6 @@ const SettingPanel = ({
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
<Tooltip title='Скрыть все столбцы кроме текущих'>
|
||||
<IconButton
|
||||
variant='outlined'
|
||||
color='success'
|
||||
onClick={handleShowOnlyCurrentStageColumns}
|
||||
disabled={!columnsCurStage || columnsCurStage.length === 0}
|
||||
>
|
||||
<Eye />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</GroupByObject>
|
||||
<Divider orientation='vertical' flexItem />
|
||||
<ZoomSlider onChange={onChangeSizeMult} curScale={sizeMult} />
|
||||
|
||||
@ -1,62 +1,62 @@
|
||||
export const greenColumn = {
|
||||
ROOT: "#C2D59A",
|
||||
GROUP: "#D7E3BC",
|
||||
ITEM: "#EAF0DD",
|
||||
SUB_ITEM: "#EAF0DD",
|
||||
INPUT: "#FFFFFF",
|
||||
COLOR: "#000000",
|
||||
ROOT: '#C2D59A',
|
||||
GROUP: '#D7E3BC',
|
||||
ITEM: '#EAF0DD',
|
||||
SUB_ITEM: '#FFFFFF',
|
||||
INPUT: '#FFFFFF',
|
||||
COLOR: '#000000',
|
||||
};
|
||||
|
||||
export const orangeColumn = {
|
||||
ROOT: "#F1C297",
|
||||
GROUP: "#F6D6B9",
|
||||
ITEM: "#FAEBDC",
|
||||
SUB_ITEM: "#FAEBDC",
|
||||
INPUT: "#FFFFFF",
|
||||
COLOR: "#000000",
|
||||
ROOT: '#F1C297',
|
||||
GROUP: '#F6D6B9',
|
||||
ITEM: '#FAEBDC',
|
||||
SUB_ITEM: '#FFFFFF',
|
||||
INPUT: '#FFFFFF',
|
||||
COLOR: '#000000',
|
||||
};
|
||||
|
||||
export const whiteColumn = {
|
||||
ROOT: "#FFFFFF",
|
||||
GROUP: "#FFFFFF",
|
||||
ITEM: "#ffffff",
|
||||
SUB_ITEM: "#ffffff",
|
||||
INPUT: "#FFFFFF",
|
||||
COLOR: "#000000",
|
||||
ROOT: '#FFFFFF',
|
||||
GROUP: '#FFFFFF',
|
||||
ITEM: '#ffffff',
|
||||
SUB_ITEM: '#FFFFFF',
|
||||
INPUT: '#FFFFFF',
|
||||
COLOR: '#000000',
|
||||
};
|
||||
|
||||
export const redColumn = {
|
||||
ROOT: "#933634",
|
||||
GROUP: "#D89493",
|
||||
ITEM: "#E5B7B6",
|
||||
SUB_ITEM: "#E5B7B6",
|
||||
INPUT: "#FFFFFF",
|
||||
COLOR: "#ffffff",
|
||||
ROOT: '#933634', // самый темный
|
||||
GROUP: '#D89493', // средний
|
||||
ITEM: '#E5B7B6', // самый светлый
|
||||
SUB_ITEM: '#FFFFFF',
|
||||
INPUT: '#FFFFFF',
|
||||
COLOR: '#ffffff',
|
||||
};
|
||||
|
||||
export const blueColumn = {
|
||||
ROOT: "#538DD4",
|
||||
GROUP: "#8DB3E2",
|
||||
ITEM: "#C5D8F0",
|
||||
SUB_ITEM: "#C5D8F0",
|
||||
INPUT: "#FFFFFF",
|
||||
COLOR: "#000000",
|
||||
ROOT: '#538DD4', // самый темный
|
||||
GROUP: '#8DB3E2', // средний
|
||||
ITEM: '#C5D8F0', // самый светлый
|
||||
SUB_ITEM: '#FFFFFF',
|
||||
INPUT: '#FFFFFF',
|
||||
COLOR: '#000000',
|
||||
};
|
||||
|
||||
export const tealColumn = {
|
||||
ROOT: "#92CDDC",
|
||||
GROUP: "#B6DDE8",
|
||||
ITEM: "rgb(211, 228, 248)",
|
||||
SUB_ITEM: "rgb(211, 228, 248)",
|
||||
INPUT: "#FFFFFF",
|
||||
COLOR: "#000000",
|
||||
ROOT: '#92CDDC', // самый темный
|
||||
GROUP: '#B6DDE8', // средний
|
||||
ITEM: 'rgb(211, 228, 248)', // самый светлый
|
||||
SUB_ITEM: '#FFFFFF',
|
||||
INPUT: '#FFFFFF',
|
||||
COLOR: '#000000',
|
||||
};
|
||||
|
||||
export const yellowColumn = {
|
||||
ROOT: "#FFFF66",
|
||||
GROUP: "#FFFF99",
|
||||
ITEM: "#FFFFCC",
|
||||
SUB_ITEM: "#FFFFCC",
|
||||
INPUT: "#FFFFFF",
|
||||
COLOR: "#000000",
|
||||
ROOT: '#FFFF66',
|
||||
GROUP: '#FFFF99',
|
||||
ITEM: '#FFFFCC',
|
||||
SUB_ITEM: '#FFFFFF',
|
||||
INPUT: '#FFFFFF',
|
||||
COLOR: '#000000',
|
||||
};
|
||||
|
||||
@ -1,45 +1,28 @@
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { toast } from "react-toastify";
|
||||
import { useWebSocket } from "../hooks/useWebSocket";
|
||||
import { getRowId } from "../utils/rowUtils";
|
||||
import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useWebSocket } from '../hooks/useWebSocket';
|
||||
import { getRowId } from '../utils/rowUtils';
|
||||
|
||||
const RealtimeContext = createContext(null);
|
||||
|
||||
export const useRealtime = () => {
|
||||
const context = useContext(RealtimeContext);
|
||||
if (!context) {
|
||||
throw new Error("useRealtime must be used within RealtimeProvider");
|
||||
throw new Error('useRealtime must be used within RealtimeProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
export const RealtimeProvider = ({
|
||||
children,
|
||||
formId,
|
||||
sheetName,
|
||||
direction,
|
||||
year,
|
||||
userId,
|
||||
isProject = false,
|
||||
}) => {
|
||||
export const RealtimeProvider = ({ children, formId, sheetName, direction, year, userId, isProject = false }) => {
|
||||
// Формируем 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")}${
|
||||
!isProject
|
||||
? `/api/v1/ws/form/${formId}/sheet/${sheetName}${direction ? `?direction=${direction}` : ""}`
|
||||
: `/api/v1/ws/projects/${formId}/report/${year}/${sheetName}`
|
||||
}`;
|
||||
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',
|
||||
)}` +
|
||||
(!isProject
|
||||
? `/api/v1/ws/form/${formId}/sheet/${sheetName}${direction ? `?direction=${direction}` : ''}`
|
||||
: `/api/v1/ws/projects/${formId}/report/${year}/${sheetName}`);
|
||||
|
||||
//для ячеек которые редактируются другими пользователями
|
||||
const [lockedCells, setLockedCells] = useState([]);
|
||||
@ -47,68 +30,65 @@ export const RealtimeProvider = ({
|
||||
// Обработка ошибок
|
||||
if (data.error) {
|
||||
console.log(data.error.message);
|
||||
toast.error(`Server error:${data.error.message}` || "");
|
||||
toast.error('Server error:' + data.error.message || '');
|
||||
onErrorRef.current?.(data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (data.event) {
|
||||
case "cell_edit_start":
|
||||
console.log("Cell edit started:", data);
|
||||
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);
|
||||
case 'cell_edit_end':
|
||||
console.log('Cell edit ended:', data);
|
||||
if (onCellEditEndRef.current) {
|
||||
onCellEditEndRef.current(data);
|
||||
}
|
||||
break;
|
||||
|
||||
case "cell_updated":
|
||||
case 'cell_updated':
|
||||
if (data.result && onCellUpdateRef.current) {
|
||||
const updatedCells = Array.isArray(data.result)
|
||||
? data.result
|
||||
: [data.result];
|
||||
const updatedCells = Array.isArray(data.result) ? data.result : [data.result];
|
||||
onCellUpdateRef.current(updatedCells);
|
||||
}
|
||||
break;
|
||||
|
||||
case "row_added":
|
||||
case 'row_added':
|
||||
if (data.result && onRowAddRef.current) {
|
||||
onRowAddRef.current(data);
|
||||
}
|
||||
break;
|
||||
|
||||
case "row_deleted":
|
||||
case 'row_deleted':
|
||||
if (data.result && onRowDeleteRef.current) {
|
||||
onRowDeleteRef.current(data);
|
||||
}
|
||||
break;
|
||||
|
||||
case "program_added":
|
||||
console.log("Program added:", data);
|
||||
case 'program_added':
|
||||
console.log('Program added:', data);
|
||||
if (data.result && onProgramAddRef.current) {
|
||||
onProgramAddRef.current(data);
|
||||
}
|
||||
break;
|
||||
|
||||
case "project_added":
|
||||
console.log("Project added:", data);
|
||||
case 'project_added':
|
||||
console.log('Project added:', data);
|
||||
if (data.result && onProjectAddRef.current) {
|
||||
onProjectAddRef.current(data);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log("Unknown event:", data.event);
|
||||
console.log('Unknown event:', data.event);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const { isConnectedRef, isConnected, sendMessage, disconnect, lastError } =
|
||||
useWebSocket(wsUrl, handleMessage);
|
||||
const { isConnectedRef, isConnected, sendMessage, disconnect, lastError } = useWebSocket(wsUrl, handleMessage);
|
||||
|
||||
const onCellUpdateRef = useRef(null);
|
||||
const onRowAddRef = useRef(null);
|
||||
@ -125,16 +105,16 @@ export const RealtimeProvider = ({
|
||||
// Функция для получения токена из localStorage
|
||||
const getAccessToken = useCallback(() => {
|
||||
try {
|
||||
const token = localStorage.getItem("access_token");
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
if (!token) {
|
||||
console.warn("No access token found in localStorage");
|
||||
console.warn('No access token found in localStorage');
|
||||
return null;
|
||||
}
|
||||
|
||||
return token;
|
||||
} catch (error) {
|
||||
console.error("Error reading token from localStorage:", error);
|
||||
console.error('Error reading token from localStorage:', error);
|
||||
return null;
|
||||
}
|
||||
}, []);
|
||||
@ -142,13 +122,13 @@ export const RealtimeProvider = ({
|
||||
const sendLogin = useCallback(() => {
|
||||
const token = getAccessToken();
|
||||
if (!token) {
|
||||
console.error("Cannot login: no token available");
|
||||
onErrorRef.current?.("Токен авторизации не найден");
|
||||
console.error('Cannot login: no token available');
|
||||
onErrorRef.current?.('Токен авторизации не найден');
|
||||
return false;
|
||||
}
|
||||
|
||||
const loginMessage = {
|
||||
event: "user_login",
|
||||
event: 'user_login',
|
||||
data: { token },
|
||||
};
|
||||
|
||||
@ -158,7 +138,7 @@ export const RealtimeProvider = ({
|
||||
useEffect(() => {
|
||||
if (isConnected && !authAttemptedRef.current) {
|
||||
authAttemptedRef.current = true;
|
||||
console.log("WebSocket connected, sending login...");
|
||||
console.log('WebSocket connected, sending login...');
|
||||
setTimeout(() => {
|
||||
sendLogin();
|
||||
}, 100);
|
||||
@ -191,7 +171,7 @@ export const RealtimeProvider = ({
|
||||
const rowId = row.id;
|
||||
|
||||
if (!isConnectedRef) {
|
||||
onErrorRef.current?.("WebSocket не подключен");
|
||||
onErrorRef.current?.('WebSocket не подключен');
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -199,7 +179,7 @@ export const RealtimeProvider = ({
|
||||
const colId = columnId.slice(5); // убираем префикс "data."
|
||||
|
||||
const message = {
|
||||
event: "cell_edit_start",
|
||||
event: 'cell_edit_start',
|
||||
data: {
|
||||
line_id: lineId,
|
||||
column: colId,
|
||||
@ -223,7 +203,7 @@ export const RealtimeProvider = ({
|
||||
const rowId = row.id;
|
||||
|
||||
if (!isConnectedRef) {
|
||||
onErrorRef.current?.("WebSocket не подключен");
|
||||
onErrorRef.current?.('WebSocket не подключен');
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -231,7 +211,7 @@ export const RealtimeProvider = ({
|
||||
const colId = columnId.slice(5);
|
||||
|
||||
const message = {
|
||||
event: "cell_edit_end",
|
||||
event: 'cell_edit_end',
|
||||
data: {
|
||||
line_id: lineId,
|
||||
column: colId,
|
||||
@ -253,14 +233,14 @@ export const RealtimeProvider = ({
|
||||
const columnId = column.id;
|
||||
const rowId = row.id;
|
||||
if (!isConnectedRef) {
|
||||
onErrorRef.current?.("WebSocket не подключен");
|
||||
onErrorRef.current?.('WebSocket не подключен');
|
||||
return false;
|
||||
}
|
||||
|
||||
const lineId = getRowId(row);
|
||||
const colId = columnId.slice(5);
|
||||
const message = {
|
||||
event: "cell_updated",
|
||||
event: 'cell_updated',
|
||||
data: {
|
||||
line_id: lineId,
|
||||
line_id_code: row.id,
|
||||
@ -282,12 +262,12 @@ export const RealtimeProvider = ({
|
||||
const addRow = useCallback(
|
||||
async (rowData) => {
|
||||
if (!isConnectedRef) {
|
||||
onErrorRef.current?.("Нет подключения или авторизации");
|
||||
onErrorRef.current?.('Нет подключения или авторизации');
|
||||
return false;
|
||||
}
|
||||
|
||||
const message = {
|
||||
event: "row_added",
|
||||
event: 'row_added',
|
||||
data: rowData,
|
||||
};
|
||||
addCommonField(message);
|
||||
@ -300,12 +280,12 @@ export const RealtimeProvider = ({
|
||||
const addProject = useCallback(
|
||||
async (rowData) => {
|
||||
if (!isConnectedRef) {
|
||||
onErrorRef.current?.("Нет подключения или авторизации");
|
||||
onErrorRef.current?.('Нет подключения или авторизации');
|
||||
return false;
|
||||
}
|
||||
|
||||
const message = {
|
||||
event: "project_added",
|
||||
event: 'project_added',
|
||||
data: rowData,
|
||||
};
|
||||
addCommonField(message);
|
||||
@ -318,12 +298,12 @@ export const RealtimeProvider = ({
|
||||
const addProgram = useCallback(
|
||||
async (rowData) => {
|
||||
if (!isConnectedRef) {
|
||||
onErrorRef.current?.("Нет подключения или авторизации");
|
||||
onErrorRef.current?.('Нет подключения или авторизации');
|
||||
return false;
|
||||
}
|
||||
|
||||
const message = {
|
||||
event: "program_added",
|
||||
event: 'program_added',
|
||||
data: rowData,
|
||||
};
|
||||
addCommonField(message);
|
||||
@ -336,12 +316,12 @@ export const RealtimeProvider = ({
|
||||
const deleteRow = useCallback(
|
||||
async (rowId) => {
|
||||
if (!isConnectedRef) {
|
||||
onErrorRef.current?.("Нет подключения или авторизации");
|
||||
onErrorRef.current?.('Нет подключения или авторизации');
|
||||
return false;
|
||||
}
|
||||
|
||||
const message = {
|
||||
event: "row_deleted",
|
||||
event: 'row_deleted',
|
||||
data: { row_id: rowId },
|
||||
};
|
||||
addCommonField(message);
|
||||
@ -446,8 +426,7 @@ export const RealtimeProvider = ({
|
||||
lockedCells,
|
||||
setLockedCells,
|
||||
releaseAllLocks: () => releaseAllLocks(userId),
|
||||
}}
|
||||
>
|
||||
}}>
|
||||
{children}
|
||||
</RealtimeContext.Provider>
|
||||
);
|
||||
|
||||
@ -1,20 +1,13 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
import { FormsSheetApi } from "../../../api/form_sheet";
|
||||
import { ProjectsApi } from "../../../api/projects";
|
||||
import { useRealtime } from "../contexts/RealtimeContext";
|
||||
import {
|
||||
addRootRow,
|
||||
deleteRow,
|
||||
insertCell,
|
||||
insertProjectRow,
|
||||
updateCells,
|
||||
} from "../utils/cellUtils";
|
||||
import { getParentId, isVspNewRow } from "../utils/rowUtils";
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { FormsSheetApi } from '../../../api/form_sheet';
|
||||
import { ProjectsApi } from '../../../api/projects';
|
||||
import { useRealtime } from '../contexts/RealtimeContext';
|
||||
import { addRootRow, deleteRow, insertCell, insertProjectRow, updateCells } from '../utils/cellUtils';
|
||||
import { getParentId, isVspNewRow } from '../utils/rowUtils';
|
||||
|
||||
const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
||||
const [data, setData] = useState([]);
|
||||
const [columnsCurStage, setColumnsCurStage] = useState([]);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
@ -88,7 +81,7 @@ const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
||||
setIsLoading(true);
|
||||
let res;
|
||||
|
||||
if (formType === "PROJECT") {
|
||||
if (formType == 'PROJECT') {
|
||||
res = await ProjectsApi.getTableData(formId, sheetName, year);
|
||||
} else if (direction !== null) {
|
||||
res = await FormsSheetApi.get(formId, sheetName, {
|
||||
@ -98,14 +91,6 @@ const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
||||
res = await FormsSheetApi.get(formId, sheetName);
|
||||
}
|
||||
const data = res.result.filter((d) => d.depth > -1);
|
||||
const curStageColumns = res.result.filter((d) => d.depth === -1)[0];
|
||||
if (curStageColumns) {
|
||||
const cols = [];
|
||||
for (const col of curStageColumns.data.editable) {
|
||||
cols.push(`data.${col.column}`);
|
||||
}
|
||||
setColumnsCurStage(cols);
|
||||
}
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
setData([]);
|
||||
@ -115,7 +100,7 @@ const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
||||
const hierarchicalData = buildHierarchy(data);
|
||||
setData(hierarchicalData);
|
||||
} catch (err) {
|
||||
console.error("Failed to load initial data:", err);
|
||||
console.error('Failed to load initial data:', err);
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@ -143,10 +128,7 @@ const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
||||
for (const item of items) {
|
||||
const currentLevel = getTypeLevel(item.row_type);
|
||||
|
||||
while (
|
||||
stack.length > 0 &&
|
||||
getTypeLevel(stack[stack.length - 1].row_type) >= currentLevel
|
||||
) {
|
||||
while (stack.length > 0 && getTypeLevel(stack[stack.length - 1].row_type) >= currentLevel) {
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
@ -164,7 +146,7 @@ const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
||||
}
|
||||
|
||||
// Добавляем текущий узел в стек, если он может иметь детей
|
||||
if (item.row_type !== "INPUT") {
|
||||
if (item.row_type !== 'INPUT') {
|
||||
stack.push(newNode);
|
||||
}
|
||||
}
|
||||
@ -213,12 +195,12 @@ const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
||||
[[], null],
|
||||
);
|
||||
if (newRow === null) {
|
||||
toast.error("Ошибка получения данных");
|
||||
toast.error('Ошибка получения данных');
|
||||
return;
|
||||
}
|
||||
setData((prevData) => {
|
||||
let data = structuredClone(prevData);
|
||||
if (prevData[0]?.row_type !== "ROOT") {
|
||||
if (prevData[0]?.row_type !== 'ROOT') {
|
||||
const newRowRoot = updates[0];
|
||||
data = addRootRow(data, newRowRoot);
|
||||
}
|
||||
@ -233,7 +215,7 @@ const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
||||
const program_id = data.data.program_id;
|
||||
const [updatedCells, newRow] = updates.reduce(
|
||||
(res, row) => {
|
||||
if (row[0] === "ITEM") {
|
||||
if (row[0] === 'ITEM') {
|
||||
res[1] = row;
|
||||
} else {
|
||||
res[0].push(row);
|
||||
@ -243,7 +225,7 @@ const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
||||
[[], null],
|
||||
);
|
||||
if (newRow === null) {
|
||||
toast.error("Ошибка получения данных");
|
||||
toast.error('Ошибка получения данных');
|
||||
return;
|
||||
}
|
||||
setData((prevData) => {
|
||||
@ -256,7 +238,7 @@ const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
||||
const unsubscribeProgramAdds = subscribeToProgramAdds((msg) => {
|
||||
let newRow = msg.result[1][0];
|
||||
setData((prevData) => {
|
||||
const hasRoot = prevData.length > 0 && prevData[0].row_type === "ROOT";
|
||||
const hasRoot = prevData.length > 0 && prevData[0].row_type === 'ROOT';
|
||||
if (hasRoot) {
|
||||
newRow = msg.result[1][1];
|
||||
}
|
||||
@ -291,7 +273,7 @@ const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
||||
// Обработчик начала редактирования ячейки
|
||||
const unsubscribeCellEditStart = subscribeToCellEditStart((data) => {
|
||||
const dataCell = data.data;
|
||||
dataCell.column = "data." + dataCell.column;
|
||||
dataCell.column = 'data.' + dataCell.column;
|
||||
if (data.is_self) {
|
||||
setEditingCells(dataCell);
|
||||
return;
|
||||
@ -311,14 +293,14 @@ const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
||||
return;
|
||||
}
|
||||
const dataCell = data.data;
|
||||
dataCell.column = `data.${dataCell.column}`;
|
||||
dataCell.column = 'data.' + dataCell.column;
|
||||
const { line_id, column } = dataCell;
|
||||
const cellKey = `${line_id}_${column}`;
|
||||
setLockedCells((prev) => prev.filter((c) => c !== cellKey));
|
||||
});
|
||||
|
||||
const unsubscribeErrors = subscribeToErrors((err) => {
|
||||
console.error("WebSocket error:", err);
|
||||
console.error('WebSocket error:', err);
|
||||
setError(err);
|
||||
setTimeout(() => setError(null), 5000);
|
||||
});
|
||||
@ -368,7 +350,6 @@ const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
||||
return {
|
||||
data,
|
||||
setData,
|
||||
columnsCurStage,
|
||||
isConnected,
|
||||
isLoading,
|
||||
error,
|
||||
|
||||
@ -90,7 +90,6 @@ export const getTableColumns = ({
|
||||
Cell,
|
||||
EditCell,
|
||||
columnsConfig,
|
||||
columnsCurStage,
|
||||
onCellUpdateError,
|
||||
onCellNumberClick,
|
||||
isCellInvalid,
|
||||
@ -101,13 +100,6 @@ export const getTableColumns = ({
|
||||
const cellPropsCache = new WeakMap();
|
||||
const editPropsCache = new WeakMap();
|
||||
|
||||
const isEditable = (row, column) => {
|
||||
if (row.original?.row_type !== 'INPUT') return false;
|
||||
const keyCol = column.id;
|
||||
if (columnsCurStage.includes(keyCol)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
const getCachedCellProps = (row, column, table) => {
|
||||
const key = `${row.id}_${column.id}`;
|
||||
const value = row.getValue(column.id);
|
||||
@ -137,7 +129,7 @@ export const getTableColumns = ({
|
||||
const props = {
|
||||
globalFilter,
|
||||
columnFilter,
|
||||
isEditable: isEditable(row, column),
|
||||
isEditable: row.original?.row_type === 'INPUT' || false,
|
||||
backgroundColor: getColorCell(columnColors, column, row),
|
||||
isUpdating: table.options.meta?.updatingCells?.[`${row.id}_${column.id}`],
|
||||
isInvalid,
|
||||
@ -162,7 +154,7 @@ export const getTableColumns = ({
|
||||
}
|
||||
|
||||
const props = {
|
||||
isEditable: isEditable(row, column),
|
||||
isEditable: row.original?.row_type === 'INPUT' || false,
|
||||
};
|
||||
|
||||
rowCache[key] = props;
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
// Колонки, которые не показываются при выборе колонок этапа
|
||||
export const stageColumnsHiddenFromPicker = [
|
||||
'sort_order',
|
||||
'data.header.section',
|
||||
'data.header.item_id',
|
||||
'data.header.num_group',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user