diff --git a/src/components/DualSpreadsheet/DualSpreadsheet.tsx b/src/components/DualSpreadsheet/DualSpreadsheet.tsx index bee583a..43ffe19 100644 --- a/src/components/DualSpreadsheet/DualSpreadsheet.tsx +++ b/src/components/DualSpreadsheet/DualSpreadsheet.tsx @@ -11,6 +11,7 @@ import { } from 'react' import { VariableSizeGrid } from 'react-window' import { useMultiSheetEngine } from '../../hooks/useMultiSheetEngine' +import { MergedCell } from '../../types/template' const ROW_COUNT = 90 const COL_COUNT = 20 @@ -26,9 +27,59 @@ type SheetType = keyof typeof SHEET_NAMES // --- Helper Functions --- const getColumnLabel = (index: number) => String.fromCharCode(65 + index) +// Функция для преобразования адреса ячейки в координаты +const cellAddressToCoords = (address: string): { row: number; col: number } => { + const match = address.match(/^([A-Z]+)(\d+)$/) + if (!match) return { row: 0, col: 0 } + + const col = match[1].charCodeAt(0) - 65 // Простое преобразование для одной буквы + const row = parseInt(match[2]) - 1 + + return { row, col } +} + +// Функция для проверки, является ли ячейка частью объединенной группы +const findMergedCellInfo = ( + row: number, + col: number, + mergedCells: MergedCell[], +): { + isMerged: boolean + isMainCell: boolean + mergedCell?: MergedCell + spans?: { rowSpan: number; colSpan: number } +} => { + for (const merged of mergedCells) { + const fromCoords = cellAddressToCoords(merged.from) + const toCoords = cellAddressToCoords(merged.to) + + // Проверяем, попадает ли текущая ячейка в диапазон объединения + if ( + row >= fromCoords.row && + row <= toCoords.row && + col >= fromCoords.col && + col <= toCoords.col + ) { + const isMainCell = row === fromCoords.row && col === fromCoords.col + const rowSpan = toCoords.row - fromCoords.row + 1 + const colSpan = toCoords.col - fromCoords.col + 1 + + return { + isMerged: true, + isMainCell, + mergedCell: merged, + spans: { rowSpan, colSpan }, + } + } + } + + return { isMerged: false, isMainCell: false } +} + // --- Interfaces --- interface DualSpreadsheetProps { templateData?: Record> + mergedCells?: MergedCell[] // Добавляем поддержку объединенных ячеек } // Кэшированные данные ячеек для оптимизации @@ -45,6 +96,7 @@ interface CellProps { rowIndex: number colIndex: number columnWidth: number + columnWidths: number[] // Добавляем массив всех ширин колонок isSelected: boolean isEditing: boolean displayValue: string // Отображаемое значение (вычисленное) @@ -58,6 +110,14 @@ interface CellProps { setActiveSheet: (type: SheetType) => void activeCellRef: React.RefObject style: CSSProperties // Добавляем style для react-window + mergedCellInfo?: { + // Информация об объединенных ячейках + isMerged: boolean + isMainCell: boolean + rowSpan?: number + colSpan?: number + mergedValue?: any + } } const Cell = memo( @@ -66,6 +126,7 @@ const Cell = memo( rowIndex, colIndex, columnWidth, + columnWidths, isSelected, isEditing, displayValue, @@ -79,17 +140,63 @@ const Cell = memo( setActiveSheet, activeCellRef, style, // Добавляем style для react-window + mergedCellInfo, }: CellProps) => { + // Если ячейка объединена, но не является основной, не рендерим её + if (mergedCellInfo?.isMerged && !mergedCellInfo.isMainCell) { + return null + } + const className = `border border-border ${isSelected ? 'bg-accent' : ''} ${ isModified ? 'bg-yellow-50' : '' }` - const cellStyle: CSSProperties = { + // Для объединенных ячеек нужно рассчитать правильные размеры + let cellStyle: CSSProperties = { ...style, overflow: 'visible', padding: 0, } + // Если это основная ячейка объединенной группы, расширяем её размеры + if ( + mergedCellInfo?.isMainCell && + mergedCellInfo.rowSpan && + mergedCellInfo.colSpan + ) { + const { rowSpan, colSpan } = mergedCellInfo + + // Рассчитываем общую ширину для объединенной ячейки + let totalWidth = 0 + for (let i = 0; i < colSpan; i++) { + const currentColIndex = colIndex + i + if (currentColIndex < columnWidths.length) { + totalWidth += columnWidths[currentColIndex] + } + } + + // Рассчитываем общую высоту для объединенной ячейки + const totalHeight = ROW_HEIGHT * rowSpan + + cellStyle = { + ...cellStyle, + width: totalWidth, + height: totalHeight, + zIndex: 10, // Поднимаем выше обычных ячеек + backgroundColor: isSelected + ? 'hsl(var(--accent))' + : isModified + ? '#fefce8' + : 'white', + } + } + + // Используем значение из объединенной ячейки, если доступно + const cellDisplayValue = + mergedCellInfo?.isMainCell && mergedCellInfo.mergedValue !== undefined + ? String(mergedCellInfo.mergedValue) + : displayValue + const handleClick = useCallback(() => { setActiveSheet(sheetType) onCellClick(rowIndex, colIndex) @@ -145,7 +252,7 @@ const Cell = memo( maxWidth: hasRightContent ? `${columnWidth - 16}px` : 'none', }} > - {displayValue} + {cellDisplayValue} )} @@ -271,6 +378,7 @@ const CellRenderer = ({ isEditing, editingValue, cachedData, + mergedCells, handleCellClick, handleCellDoubleClick, handleCellChange, @@ -293,6 +401,22 @@ const CellRenderer = ({ ? '' // Не показывать вычисленное значение во время редактирования : cellData?.displayValue || '' + // Получаем информацию об объединенных ячейках только для листа отчета + const mergedCellInfo = + sheetType === 'report' && mergedCells + ? findMergedCellInfo(rowIndex, columnIndex, mergedCells) + : { isMerged: false, isMainCell: false } + + const mergedCellProps = mergedCellInfo.isMerged + ? { + isMerged: true, + isMainCell: mergedCellInfo.isMainCell, + rowSpan: mergedCellInfo.spans?.rowSpan, + colSpan: mergedCellInfo.spans?.colSpan, + mergedValue: mergedCellInfo.mergedCell?.value, + } + : undefined + return ( ) } @@ -349,6 +475,7 @@ interface SpreadsheetProps { colIndex: number, newWidth: number, ) => void + mergedCells?: MergedCell[] // Добавляем поддержку объединенных ячеек } // --- Spreadsheet Component --- @@ -374,6 +501,7 @@ const Spreadsheet = ({ gridRefs, onGridScroll, handleColumnResize, + mergedCells, }: SpreadsheetProps) => { const widths = columnWidths const cachedData = getCachedCellData(sheetType) @@ -386,6 +514,7 @@ const Spreadsheet = ({ isEditing, editingValue, cachedData, + mergedCells, handleCellClick, handleCellDoubleClick, handleCellChange, @@ -530,7 +659,10 @@ const Spreadsheet = ({ ) } -const DualSpreadsheet: FC = ({ templateData = {} }) => { +const DualSpreadsheet: FC = ({ + templateData = {}, + mergedCells = [], +}) => { const { revision, updateRevision, ...multiSheetEngine } = useMultiSheetEngine( { sheets: [ @@ -996,6 +1128,7 @@ const DualSpreadsheet: FC = ({ templateData = {} }) => { gridRefs={gridRefs} onGridScroll={onGridScroll} handleColumnResize={handleColumnResize} + mergedCells={mergedCells} />
= ({ templateData = {} }) => { gridRefs={gridRefs} onGridScroll={onGridScroll} handleColumnResize={handleColumnResize} + mergedCells={mergedCells} />
diff --git a/src/components/TemplateManager/TemplateEditor.tsx b/src/components/TemplateManager/TemplateEditor.tsx index f5a90e5..6e5b1af 100644 --- a/src/components/TemplateManager/TemplateEditor.tsx +++ b/src/components/TemplateManager/TemplateEditor.tsx @@ -240,7 +240,11 @@ export const TemplateEditor: React.FC = ({ {/* Таблицы - теперь занимают всё оставшееся пространство */}
- +
)