import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; import ChevronRightIcon from '@mui/icons-material/ChevronRight'; import ErrorOutlineIcon from '@mui/icons-material/ErrorOutlineOutlined'; import { Button, IconButton, ListSubheader, Menu, MenuItem, Stack } from '@mui/material'; import { memo, useEffect, useMemo, useState } from 'react'; import { groupValidationErrors } from '../utils/validationUtils'; // AURORA-1893: «более выделенный формат контроля за ошибками» — красная плашка // вместо прежней бледно-оранжевой. const errorColors = { bg: '#FDEDED', text: '#D32F2F', border: '#D32F2F', hover: '#FBD5D5', }; const stepButtonSx = { color: errorColors.text, border: `1px solid ${errorColors.border}`, borderRadius: '0.5rem', '&:hover': { bgcolor: errorColors.hover }, '&.Mui-disabled': { color: errorColors.border, border: `1px solid ${errorColors.border}`, opacity: 0.5 }, }; // Внутри одной группы (правило + колонка) показываем не больше 20 строк, // остальные — свёрнутой строкой «и ещё N». const MAX_VISIBLE_CELLS_PER_GROUP = 20; const getColumnTitle = (headerPaths, columnId) => headerPaths?.[columnId]?.filter(Boolean).join(' / ') || columnId; const ValidationErrorsAlert = ({ errors = [], onNavigateToError, headerPaths }) => { const [anchorEl, setAnchorEl] = useState(null); const [activeIndex, setActiveIndex] = useState(0); const isOpen = Boolean(anchorEl); const groups = useMemo(() => groupValidationErrors(errors), [errors]); useEffect(() => { if (errors.length === 0) { setAnchorEl(null); setActiveIndex(0); return; } if (activeIndex >= errors.length) { setActiveIndex(0); } }, [errors, activeIndex]); if (errors.length === 0) return null; const navigateToIndex = (index) => { const error = errors[index]; if (!error) return; setActiveIndex(index); onNavigateToError?.({ rowKey: error.rowKey, columnId: error.columnId }); }; const handleStep = (direction) => { const nextIndex = (activeIndex + direction + errors.length) % errors.length; navigateToIndex(nextIndex); }; const handleSelectCell = (cell) => { const index = errors.findIndex((candidate) => candidate.id === cell.id); navigateToIndex(index < 0 ? 0 : index); setAnchorEl(null); }; return ( <> handleStep(-1)} aria-label='Предыдущая ошибка' sx={stepButtonSx}> handleStep(1)} aria-label='Следующая ошибка' sx={stepButtonSx}> setAnchorEl(null)} anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }} transformOrigin={{ vertical: 'top', horizontal: 'left' }} slotProps={{ paper: { 'data-validation-errors-menu': true, sx: { mt: 0.5, bgcolor: errorColors.bg, color: errorColors.text, border: `1px solid ${errorColors.border}`, borderRadius: '0.5rem', boxShadow: 'none', minWidth: anchorEl?.offsetWidth, maxWidth: 420, maxHeight: 420, }, }, }}> {groups.flatMap((group) => { const columnTitle = getColumnTitle(headerPaths, group.columnId); const visibleCells = group.cells.slice(0, MAX_VISIBLE_CELLS_PER_GROUP); const hiddenCount = group.count - visibleCells.length; return [ {group.message} · {columnTitle} ({group.count}) , ...visibleCells.map((cell) => ( handleSelectCell(cell)} sx={{ whiteSpace: 'normal', color: errorColors.text, cursor: 'pointer', pl: 3, '&:hover': { bgcolor: errorColors.hover, }, }}> {cell.rowLabel} )), ...(hiddenCount > 0 ? [ и ещё {hiddenCount} , ] : []), ]; })} ); }; export default memo(ValidationErrorsAlert);