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