Compare commits
No commits in common. "42c766cac941a3fcd168f1f5ce3aefeca2dcdf99" and "1c9650ba4f38a636b6a73aa33139b89dbca539a9" have entirely different histories.
42c766cac9
...
1c9650ba4f
@ -25,12 +25,6 @@ const LOCKED_STYLES = {
|
|||||||
opacity: 0.85,
|
opacity: 0.85,
|
||||||
};
|
};
|
||||||
|
|
||||||
const INVALID_STYLES = {
|
|
||||||
border: '2px solid #D32F2F',
|
|
||||||
backgroundColor: '#FDEDED',
|
|
||||||
color: '#C60C0C',
|
|
||||||
};
|
|
||||||
|
|
||||||
const LOCK_BADGE_STYLES = {
|
const LOCK_BADGE_STYLES = {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: '2px',
|
top: '2px',
|
||||||
@ -133,7 +127,6 @@ const CellComponent = ({
|
|||||||
isEditable,
|
isEditable,
|
||||||
isUpdating,
|
isUpdating,
|
||||||
onCellNumberClick,
|
onCellNumberClick,
|
||||||
isInvalid,
|
|
||||||
}) => {
|
}) => {
|
||||||
const { lockedCells } = useRealtime();
|
const { lockedCells } = useRealtime();
|
||||||
const cellKey = `${row.id}_${column.id}`;
|
const cellKey = `${row.id}_${column.id}`;
|
||||||
@ -169,8 +162,7 @@ const CellComponent = ({
|
|||||||
backgroundColor: isLocked ? '#f5f5f5' : backgroundColor,
|
backgroundColor: isLocked ? '#f5f5f5' : backgroundColor,
|
||||||
color: textColor,
|
color: textColor,
|
||||||
...(isLocked ? LOCKED_STYLES : {}),
|
...(isLocked ? LOCKED_STYLES : {}),
|
||||||
...(isInvalid ? INVALID_STYLES : {}),
|
}), [backgroundColor, isLocked, textColor]);
|
||||||
}), [backgroundColor, isInvalid, isLocked, textColor]);
|
|
||||||
|
|
||||||
const lockBadge = useMemo(() => {
|
const lockBadge = useMemo(() => {
|
||||||
if (!isLocked) return null;
|
if (!isLocked) return null;
|
||||||
@ -204,7 +196,6 @@ const Cell = React.memo(CellComponent, (prevProps, nextProps) => {
|
|||||||
prevProps.globalFilter === nextProps.globalFilter &&
|
prevProps.globalFilter === nextProps.globalFilter &&
|
||||||
prevProps.columnFilter === nextProps.columnFilter &&
|
prevProps.columnFilter === nextProps.columnFilter &&
|
||||||
prevProps.backgroundColor === nextProps.backgroundColor &&
|
prevProps.backgroundColor === nextProps.backgroundColor &&
|
||||||
prevProps.isInvalid === nextProps.isInvalid &&
|
|
||||||
prevProps.isEditable === nextProps.isEditable &&
|
prevProps.isEditable === nextProps.isEditable &&
|
||||||
prevProps.isUpdating === nextProps.isUpdating &&
|
prevProps.isUpdating === nextProps.isUpdating &&
|
||||||
prevProps.onCellNumberClick === nextProps.onCellNumberClick
|
prevProps.onCellNumberClick === nextProps.onCellNumberClick
|
||||||
|
|||||||
@ -15,7 +15,6 @@ import { ColumnSelectionOverlay } from './ColumnSelectionOverlay/ColumnSelection
|
|||||||
import { useColumnSettings } from './hooks/useColumnSettings';
|
import { useColumnSettings } from './hooks/useColumnSettings';
|
||||||
import { useTableScale } from './hooks/useTableScale';
|
import { useTableScale } from './hooks/useTableScale';
|
||||||
import { useHeaderPortal } from './hooks/useHeaderPortal';
|
import { useHeaderPortal } from './hooks/useHeaderPortal';
|
||||||
import { useValidationRules } from './hooks/useValidationRules';
|
|
||||||
import {
|
import {
|
||||||
BASE_TABLE_CONFIG,
|
BASE_TABLE_CONFIG,
|
||||||
getTableBodyCellProps,
|
getTableBodyCellProps,
|
||||||
@ -42,11 +41,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
|||||||
const [isLoadingData, setIsLoadingData] = useState(false);
|
const [isLoadingData, setIsLoadingData] = useState(false);
|
||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
const [editingCell, setEditingCell] = useState(null);
|
const [editingCell, setEditingCell] = useState(null);
|
||||||
const [columnsConfig, setColumnsConfig] = useState(null);
|
|
||||||
const { errors: validationErrors, isCellInvalid } = useValidationRules(
|
|
||||||
data,
|
|
||||||
columnsConfig?.columns,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [isOpenModalSelectVsp, setIsOpenModalSelectVsp] = useState(false);
|
const [isOpenModalSelectVsp, setIsOpenModalSelectVsp] = useState(false);
|
||||||
const [isOpenModalSelectExpenseItem, setIsOpenModalSelectExpenseItem] = useState(false);
|
const [isOpenModalSelectExpenseItem, setIsOpenModalSelectExpenseItem] = useState(false);
|
||||||
@ -111,6 +105,8 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
|||||||
rowVirtualizerRef.current?.measure?.();
|
rowVirtualizerRef.current?.measure?.();
|
||||||
}, [sizeMult]);
|
}, [sizeMult]);
|
||||||
|
|
||||||
|
const [columnsConfig, setColumnsConfig] = useState(null);
|
||||||
|
|
||||||
const configCache = useRef(new Map());
|
const configCache = useRef(new Map());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -159,7 +155,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
|||||||
const handleDocumentClick = (e) => {
|
const handleDocumentClick = (e) => {
|
||||||
if (e.target.closest('[data-col-select-id]')) return;
|
if (e.target.closest('[data-col-select-id]')) return;
|
||||||
if (e.target.closest('[data-pin-panel]')) return;
|
if (e.target.closest('[data-pin-panel]')) return;
|
||||||
if (e.target.closest('[data-validation-errors-menu]')) return;
|
|
||||||
|
|
||||||
setSelectedColumnId(undefined);
|
setSelectedColumnId(undefined);
|
||||||
};
|
};
|
||||||
@ -183,19 +178,12 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
|||||||
onCellUpdate: handleUpdateCell,
|
onCellUpdate: handleUpdateCell,
|
||||||
onCellNumberClick: handleClickRowCell,
|
onCellNumberClick: handleClickRowCell,
|
||||||
onCellUpdateError: handleCellUpdateError,
|
onCellUpdateError: handleCellUpdateError,
|
||||||
isCellInvalid,
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating columns:', error);
|
console.error('Error creating columns:', error);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}, [
|
}, [columnsConfig]);
|
||||||
columnsConfig,
|
|
||||||
handleCellUpdateError,
|
|
||||||
handleClickRowCell,
|
|
||||||
handleUpdateCell,
|
|
||||||
isCellInvalid,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const selectedColumnIdRef = useRef(selectedColumnId);
|
const selectedColumnIdRef = useRef(selectedColumnId);
|
||||||
|
|
||||||
@ -346,33 +334,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
|||||||
|
|
||||||
const table = useMaterialReactTable(tableConfig);
|
const table = useMaterialReactTable(tableConfig);
|
||||||
|
|
||||||
const handleNavigateToColumn = useCallback((columnId) => {
|
|
||||||
if (!columnId) return;
|
|
||||||
|
|
||||||
setSelectedColumnId(columnId);
|
|
||||||
|
|
||||||
const container = containerRef.current;
|
|
||||||
if (!container) return;
|
|
||||||
|
|
||||||
const pinnedIds = new Set(columnPinning?.left || []);
|
|
||||||
if (pinnedIds.has(columnId)) return;
|
|
||||||
|
|
||||||
const centerColumns = table
|
|
||||||
.getVisibleLeafColumns()
|
|
||||||
.filter((column) => !pinnedIds.has(column.id));
|
|
||||||
|
|
||||||
let offset = 0;
|
|
||||||
for (const column of centerColumns) {
|
|
||||||
if (column.id === columnId) break;
|
|
||||||
offset += column.getSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
container.scrollTo({
|
|
||||||
left: Math.max(0, offset - 40),
|
|
||||||
behavior: 'smooth',
|
|
||||||
});
|
|
||||||
}, [columnPinning, containerRef, table]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!dataEditingCells?.line_id) {
|
if (!dataEditingCells?.line_id) {
|
||||||
setEditingCell(null);
|
setEditingCell(null);
|
||||||
@ -542,8 +503,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
|||||||
year={year}
|
year={year}
|
||||||
isProject={formType == 'PROJECT'}
|
isProject={formType == 'PROJECT'}
|
||||||
formType={formType}
|
formType={formType}
|
||||||
validationErrors={validationErrors}
|
|
||||||
onNavigateToColumn={handleNavigateToColumn}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div style={tableWrapperStyle}>
|
<div style={tableWrapperStyle}>
|
||||||
|
|||||||
@ -2,9 +2,12 @@ import { useEffect, useState } from 'react';
|
|||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { Panel } from './SettingPanel.style';
|
import { Panel } from './SettingPanel.style';
|
||||||
import { Divider, IconButton, Stack, Tooltip } from '@mui/material';
|
import { Divider, IconButton, Stack, Tooltip } from '@mui/material';
|
||||||
|
|
||||||
import { GroupByObject } from './GroupByObject/GroupByObject';
|
import { GroupByObject } from './GroupByObject/GroupByObject';
|
||||||
|
import { ColorPickerButton } from './ColorPicker/ColorPickerButton';
|
||||||
import {
|
import {
|
||||||
AddLine,
|
AddLine,
|
||||||
|
FormulaSettingPanel,
|
||||||
Minus,
|
Minus,
|
||||||
Pin,
|
Pin,
|
||||||
Plus,
|
Plus,
|
||||||
@ -19,7 +22,6 @@ import SearchComponent from '../../common/SearchComponent';
|
|||||||
import { ExportDefaultButton } from '../../common/Buttons/ButtonsActions';
|
import { ExportDefaultButton } from '../../common/Buttons/ButtonsActions';
|
||||||
import { exportSheet, exportSheetProject } from '../../../utils/exportFile';
|
import { exportSheet, exportSheetProject } from '../../../utils/exportFile';
|
||||||
import { FORM_TYPE_OPTIONS } from '../../../constants/constants';
|
import { FORM_TYPE_OPTIONS } from '../../../constants/constants';
|
||||||
import ValidationErrorsAlert from './ValidationErrorsAlert';
|
|
||||||
|
|
||||||
const SettingPanel = ({
|
const SettingPanel = ({
|
||||||
selectedColumnId,
|
selectedColumnId,
|
||||||
@ -45,8 +47,6 @@ const SettingPanel = ({
|
|||||||
year,
|
year,
|
||||||
isProject,
|
isProject,
|
||||||
formType,
|
formType,
|
||||||
validationErrors = [],
|
|
||||||
onNavigateToColumn,
|
|
||||||
}) => {
|
}) => {
|
||||||
const [isPinned, setIsPinned] = useState(false);
|
const [isPinned, setIsPinned] = useState(false);
|
||||||
const [isExporting, setIsExporting] = useState(false);
|
const [isExporting, setIsExporting] = useState(false);
|
||||||
@ -198,10 +198,6 @@ const SettingPanel = ({
|
|||||||
/>
|
/>
|
||||||
</GroupByObject>
|
</GroupByObject>
|
||||||
</Stack>
|
</Stack>
|
||||||
<ValidationErrorsAlert
|
|
||||||
errors={validationErrors}
|
|
||||||
onNavigateToColumn={onNavigateToColumn}
|
|
||||||
/>
|
|
||||||
</Panel>
|
</Panel>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,128 +0,0 @@
|
|||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { Button, Menu, MenuItem, Stack } from '@mui/material';
|
|
||||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
|
||||||
import WarningAmberRoundedIcon from '@mui/icons-material/WarningAmberRounded';
|
|
||||||
|
|
||||||
const warningColors = {
|
|
||||||
bg: '#fff7e6',
|
|
||||||
text: '#b54708',
|
|
||||||
border: '#FCD99C',
|
|
||||||
hover: '#fff1d6',
|
|
||||||
};
|
|
||||||
|
|
||||||
const ValidationErrorsAlert = ({ errors = [], onNavigateToColumn }) => {
|
|
||||||
const [anchorEl, setAnchorEl] = useState(null);
|
|
||||||
const isOpen = Boolean(anchorEl);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (errors.length === 0) {
|
|
||||||
setAnchorEl(null);
|
|
||||||
}
|
|
||||||
}, [errors.length]);
|
|
||||||
|
|
||||||
if (errors.length === 0) return null;
|
|
||||||
|
|
||||||
const handleNavigate = (columnId) => {
|
|
||||||
onNavigateToColumn?.(columnId);
|
|
||||||
setAnchorEl(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
onClick={(e) => setAnchorEl(e.currentTarget)}
|
|
||||||
sx={{
|
|
||||||
ml: 2,
|
|
||||||
maxWidth: 360,
|
|
||||||
height: '2.5rem',
|
|
||||||
borderRadius: '0.5rem',
|
|
||||||
bgcolor: warningColors.bg,
|
|
||||||
color: warningColors.text,
|
|
||||||
borderColor: warningColors.border,
|
|
||||||
textTransform: 'none',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
px: 1.5,
|
|
||||||
gap: 1,
|
|
||||||
'&:hover': {
|
|
||||||
bgcolor: warningColors.hover,
|
|
||||||
borderColor: warningColors.border,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Stack
|
|
||||||
direction="row"
|
|
||||||
sx={{ alignItems: 'center', gap: 1, minWidth: 0, flex: 1 }}
|
|
||||||
>
|
|
||||||
<WarningAmberRoundedIcon sx={{ fontSize: '1.1rem', flexShrink: 0 }} />
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
overflow: 'hidden',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
textAlign: 'left',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Контроль ошибок
|
|
||||||
</span>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
<Stack
|
|
||||||
direction="row"
|
|
||||||
sx={{ alignItems: 'center', gap: 0.25, flexShrink: 0 }}
|
|
||||||
>
|
|
||||||
<span>Подробнее</span>
|
|
||||||
<KeyboardArrowDownIcon
|
|
||||||
sx={{
|
|
||||||
fontSize: '1.25rem',
|
|
||||||
transform: isOpen ? 'rotate(180deg)' : 'rotate(0deg)',
|
|
||||||
transition: 'transform 0.2s ease',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Menu
|
|
||||||
anchorEl={anchorEl}
|
|
||||||
open={isOpen}
|
|
||||||
onClose={() => setAnchorEl(null)}
|
|
||||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
|
|
||||||
transformOrigin={{ vertical: 'top', horizontal: 'left' }}
|
|
||||||
slotProps={{
|
|
||||||
paper: {
|
|
||||||
'data-validation-errors-menu': true,
|
|
||||||
sx: {
|
|
||||||
mt: 0.5,
|
|
||||||
bgcolor: warningColors.bg,
|
|
||||||
color: warningColors.text,
|
|
||||||
border: `1px solid ${warningColors.border}`,
|
|
||||||
borderRadius: '0.5rem',
|
|
||||||
boxShadow: 'none',
|
|
||||||
minWidth: anchorEl?.offsetWidth,
|
|
||||||
maxWidth: 420,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{errors.map((error) => (
|
|
||||||
<MenuItem
|
|
||||||
key={error.id}
|
|
||||||
onClick={() => handleNavigate(error.columnId)}
|
|
||||||
sx={{
|
|
||||||
whiteSpace: 'normal',
|
|
||||||
color: warningColors.text,
|
|
||||||
cursor: 'pointer',
|
|
||||||
'&:hover': {
|
|
||||||
bgcolor: warningColors.hover,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{error.message}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Menu>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ValidationErrorsAlert;
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
// Требование №1. Контроль превышения бронирования над сметой.
|
|
||||||
const remainingBookingKeys = [
|
|
||||||
"data.q1.rem_booking",
|
|
||||||
"data.q2.rem_booking",
|
|
||||||
"data.q3.rem_booking",
|
|
||||||
"data.q4.rem_booking"
|
|
||||||
];
|
|
||||||
|
|
||||||
// Требование №2. Контроль столбца «Текущие корректировки = 0».
|
|
||||||
const zeroValueRequirementKeys = [
|
|
||||||
"data.q1.adj_current",
|
|
||||||
"data.q2.adj_current",
|
|
||||||
"data.q3.adj_current",
|
|
||||||
"data.q4.adj_current"
|
|
||||||
];
|
|
||||||
|
|
||||||
export const validationRules = [
|
|
||||||
{
|
|
||||||
id: "remaining-booking",
|
|
||||||
columnKeys: remainingBookingKeys,
|
|
||||||
message: "Сумма бронирования превышает сумму, предусмотренную сметой",
|
|
||||||
isInvalid: (value) =>
|
|
||||||
value !== null &&
|
|
||||||
value !== undefined &&
|
|
||||||
value !== "" &&
|
|
||||||
Number.isFinite(Number(value)) &&
|
|
||||||
Number(value) < 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "current-adjustments-zero",
|
|
||||||
columnKeys: zeroValueRequirementKeys,
|
|
||||||
message: "Текущие корректировки должны быть равны 0",
|
|
||||||
appliesToRow: (row) => row?.depth === 0 || row?.row_type === "ROOT",
|
|
||||||
isInvalid: (value) =>
|
|
||||||
value !== null &&
|
|
||||||
value !== undefined &&
|
|
||||||
value !== "" &&
|
|
||||||
Number.isFinite(Number(value)) &&
|
|
||||||
Number(value) !== 0,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
import { useCallback, useMemo } from "react";
|
|
||||||
import { validationRules } from "../constants/validation";
|
|
||||||
import {
|
|
||||||
buildColumnHeaderPaths,
|
|
||||||
collectInvalidErrors,
|
|
||||||
} from "../utils/validationUtils";
|
|
||||||
|
|
||||||
export const useValidationRules = (data = [], columns = []) => {
|
|
||||||
const headerPaths = useMemo(
|
|
||||||
() => buildColumnHeaderPaths(columns),
|
|
||||||
[columns],
|
|
||||||
);
|
|
||||||
|
|
||||||
const errors = useMemo(() => {
|
|
||||||
const result = [];
|
|
||||||
const seen = new Set();
|
|
||||||
|
|
||||||
for (const rule of validationRules) {
|
|
||||||
collectInvalidErrors(data, rule, headerPaths, result, seen);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}, [data, headerPaths]);
|
|
||||||
|
|
||||||
const isCellInvalid = useCallback(
|
|
||||||
(columnId, value, row) =>
|
|
||||||
validationRules.some(
|
|
||||||
(rule) =>
|
|
||||||
rule.columnKeys.includes(columnId) &&
|
|
||||||
(!rule.appliesToRow || rule.appliesToRow(row)) &&
|
|
||||||
rule.isInvalid(value),
|
|
||||||
),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
return { errors, isCellInvalid };
|
|
||||||
};
|
|
||||||
@ -83,7 +83,6 @@ export const getTableColumns = ({
|
|||||||
columnsConfig,
|
columnsConfig,
|
||||||
onCellUpdateError,
|
onCellUpdateError,
|
||||||
onCellNumberClick,
|
onCellNumberClick,
|
||||||
isCellInvalid,
|
|
||||||
}) => {
|
}) => {
|
||||||
const columnColors = { ...columnsConfig.colors };
|
const columnColors = { ...columnsConfig.colors };
|
||||||
const columns = structuredClone(columnsConfig.columns);
|
const columns = structuredClone(columnsConfig.columns);
|
||||||
@ -93,8 +92,6 @@ export const getTableColumns = ({
|
|||||||
|
|
||||||
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 isInvalid = isCellInvalid?.(column.id, value, row.original) || false;
|
|
||||||
|
|
||||||
if (!cellPropsCache.has(row)) {
|
if (!cellPropsCache.has(row)) {
|
||||||
cellPropsCache.set(row, {});
|
cellPropsCache.set(row, {});
|
||||||
@ -107,9 +104,7 @@ export const getTableColumns = ({
|
|||||||
const currentColumnFilter = table.getState().columnFilters?.find(f => f.id === column.id)?.value;
|
const currentColumnFilter = table.getState().columnFilters?.find(f => f.id === column.id)?.value;
|
||||||
|
|
||||||
if (cached.globalFilter === currentGlobalFilter &&
|
if (cached.globalFilter === currentGlobalFilter &&
|
||||||
cached.columnFilter === currentColumnFilter &&
|
cached.columnFilter === currentColumnFilter) {
|
||||||
cached.value === value &&
|
|
||||||
cached.isInvalid === isInvalid) {
|
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,9 +118,7 @@ export const getTableColumns = ({
|
|||||||
isEditable: row.original?.row_type === 'INPUT' || false,
|
isEditable: row.original?.row_type === 'INPUT' || false,
|
||||||
backgroundColor: columnColors[column.id]?.color_type?.[row.original?.row_type || row.row_type],
|
backgroundColor: columnColors[column.id]?.color_type?.[row.original?.row_type || row.row_type],
|
||||||
isUpdating: table.options.meta?.updatingCells?.[`${row.id}_${column.id}`],
|
isUpdating: table.options.meta?.updatingCells?.[`${row.id}_${column.id}`],
|
||||||
isInvalid,
|
_hash: `${globalFilter}_${columnFilter}_${row.id}_${column.id}`,
|
||||||
value,
|
|
||||||
_hash: `${globalFilter}_${columnFilter}_${row.id}_${column.id}_${value}_${isInvalid}`,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
rowCache[key] = props;
|
rowCache[key] = props;
|
||||||
|
|||||||
@ -1,48 +0,0 @@
|
|||||||
// Достаёт вложенное поле из объекта строки таблицы
|
|
||||||
export const getValueByPath = (row, path) =>
|
|
||||||
path.split(".").reduce((value, key) => value?.[key], row);
|
|
||||||
|
|
||||||
// Строит пути заголовков колонок
|
|
||||||
export const buildColumnHeaderPaths = (columns = [], path = [], map = {}) => {
|
|
||||||
for (const column of columns) {
|
|
||||||
const nextPath = column.header ? [...path, column.header] : path;
|
|
||||||
|
|
||||||
if (column.columns?.length) {
|
|
||||||
buildColumnHeaderPaths(column.columns, nextPath, map);
|
|
||||||
} else if (column.accessorKey) {
|
|
||||||
map[column.accessorKey] = nextPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Обходит строки и собирает ошибки по правилу валидации
|
|
||||||
export const collectInvalidErrors = (rows, rule, headerPaths, errors, seen) => {
|
|
||||||
for (const row of rows) {
|
|
||||||
if (!rule.appliesToRow || rule.appliesToRow(row)) {
|
|
||||||
for (const columnKey of rule.columnKeys) {
|
|
||||||
if (!rule.isInvalid(getValueByPath(row, columnKey))) continue;
|
|
||||||
|
|
||||||
const errorId = `${rule.id}:${columnKey}`;
|
|
||||||
if (seen.has(errorId)) continue;
|
|
||||||
seen.add(errorId);
|
|
||||||
|
|
||||||
const columnPath = headerPaths[columnKey];
|
|
||||||
const columnMessage = columnPath?.length
|
|
||||||
? `Ошибка в колонке: ${columnPath.join(" => ")}`
|
|
||||||
: null;
|
|
||||||
|
|
||||||
errors.push({
|
|
||||||
id: errorId,
|
|
||||||
columnId: columnKey,
|
|
||||||
message: columnMessage
|
|
||||||
? `${rule.message}. ${columnMessage}`
|
|
||||||
: rule.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
collectInvalidErrors(row.subRows || [], rule, headerPaths, errors, seen);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Loading…
x
Reference in New Issue
Block a user