объединенные ячейки
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
|||||||
} from 'react'
|
} from 'react'
|
||||||
import { VariableSizeGrid } from 'react-window'
|
import { VariableSizeGrid } from 'react-window'
|
||||||
import { useMultiSheetEngine } from '../../hooks/useMultiSheetEngine'
|
import { useMultiSheetEngine } from '../../hooks/useMultiSheetEngine'
|
||||||
|
import { MergedCell } from '../../types/template'
|
||||||
|
|
||||||
const ROW_COUNT = 90
|
const ROW_COUNT = 90
|
||||||
const COL_COUNT = 20
|
const COL_COUNT = 20
|
||||||
@@ -26,9 +27,59 @@ type SheetType = keyof typeof SHEET_NAMES
|
|||||||
// --- Helper Functions ---
|
// --- Helper Functions ---
|
||||||
const getColumnLabel = (index: number) => String.fromCharCode(65 + index)
|
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 ---
|
// --- Interfaces ---
|
||||||
interface DualSpreadsheetProps {
|
interface DualSpreadsheetProps {
|
||||||
templateData?: Record<string, Record<string, any>>
|
templateData?: Record<string, Record<string, any>>
|
||||||
|
mergedCells?: MergedCell[] // Добавляем поддержку объединенных ячеек
|
||||||
}
|
}
|
||||||
|
|
||||||
// Кэшированные данные ячеек для оптимизации
|
// Кэшированные данные ячеек для оптимизации
|
||||||
@@ -45,6 +96,7 @@ interface CellProps {
|
|||||||
rowIndex: number
|
rowIndex: number
|
||||||
colIndex: number
|
colIndex: number
|
||||||
columnWidth: number
|
columnWidth: number
|
||||||
|
columnWidths: number[] // Добавляем массив всех ширин колонок
|
||||||
isSelected: boolean
|
isSelected: boolean
|
||||||
isEditing: boolean
|
isEditing: boolean
|
||||||
displayValue: string // Отображаемое значение (вычисленное)
|
displayValue: string // Отображаемое значение (вычисленное)
|
||||||
@@ -58,6 +110,14 @@ interface CellProps {
|
|||||||
setActiveSheet: (type: SheetType) => void
|
setActiveSheet: (type: SheetType) => void
|
||||||
activeCellRef: React.RefObject<HTMLInputElement>
|
activeCellRef: React.RefObject<HTMLInputElement>
|
||||||
style: CSSProperties // Добавляем style для react-window
|
style: CSSProperties // Добавляем style для react-window
|
||||||
|
mergedCellInfo?: {
|
||||||
|
// Информация об объединенных ячейках
|
||||||
|
isMerged: boolean
|
||||||
|
isMainCell: boolean
|
||||||
|
rowSpan?: number
|
||||||
|
colSpan?: number
|
||||||
|
mergedValue?: any
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const Cell = memo(
|
const Cell = memo(
|
||||||
@@ -66,6 +126,7 @@ const Cell = memo(
|
|||||||
rowIndex,
|
rowIndex,
|
||||||
colIndex,
|
colIndex,
|
||||||
columnWidth,
|
columnWidth,
|
||||||
|
columnWidths,
|
||||||
isSelected,
|
isSelected,
|
||||||
isEditing,
|
isEditing,
|
||||||
displayValue,
|
displayValue,
|
||||||
@@ -79,17 +140,63 @@ const Cell = memo(
|
|||||||
setActiveSheet,
|
setActiveSheet,
|
||||||
activeCellRef,
|
activeCellRef,
|
||||||
style, // Добавляем style для react-window
|
style, // Добавляем style для react-window
|
||||||
|
mergedCellInfo,
|
||||||
}: CellProps) => {
|
}: CellProps) => {
|
||||||
|
// Если ячейка объединена, но не является основной, не рендерим её
|
||||||
|
if (mergedCellInfo?.isMerged && !mergedCellInfo.isMainCell) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
const className = `border border-border ${isSelected ? 'bg-accent' : ''} ${
|
const className = `border border-border ${isSelected ? 'bg-accent' : ''} ${
|
||||||
isModified ? 'bg-yellow-50' : ''
|
isModified ? 'bg-yellow-50' : ''
|
||||||
}`
|
}`
|
||||||
|
|
||||||
const cellStyle: CSSProperties = {
|
// Для объединенных ячеек нужно рассчитать правильные размеры
|
||||||
|
let cellStyle: CSSProperties = {
|
||||||
...style,
|
...style,
|
||||||
overflow: 'visible',
|
overflow: 'visible',
|
||||||
padding: 0,
|
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(() => {
|
const handleClick = useCallback(() => {
|
||||||
setActiveSheet(sheetType)
|
setActiveSheet(sheetType)
|
||||||
onCellClick(rowIndex, colIndex)
|
onCellClick(rowIndex, colIndex)
|
||||||
@@ -145,7 +252,7 @@ const Cell = memo(
|
|||||||
maxWidth: hasRightContent ? `${columnWidth - 16}px` : 'none',
|
maxWidth: hasRightContent ? `${columnWidth - 16}px` : 'none',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{displayValue}
|
{cellDisplayValue}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -271,6 +378,7 @@ const CellRenderer = ({
|
|||||||
isEditing,
|
isEditing,
|
||||||
editingValue,
|
editingValue,
|
||||||
cachedData,
|
cachedData,
|
||||||
|
mergedCells,
|
||||||
handleCellClick,
|
handleCellClick,
|
||||||
handleCellDoubleClick,
|
handleCellDoubleClick,
|
||||||
handleCellChange,
|
handleCellChange,
|
||||||
@@ -293,6 +401,22 @@ const CellRenderer = ({
|
|||||||
? '' // Не показывать вычисленное значение во время редактирования
|
? '' // Не показывать вычисленное значение во время редактирования
|
||||||
: cellData?.displayValue || ''
|
: 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 (
|
return (
|
||||||
<Cell
|
<Cell
|
||||||
style={style}
|
style={style}
|
||||||
@@ -300,6 +424,7 @@ const CellRenderer = ({
|
|||||||
rowIndex={rowIndex}
|
rowIndex={rowIndex}
|
||||||
colIndex={columnIndex}
|
colIndex={columnIndex}
|
||||||
columnWidth={widths[columnIndex]}
|
columnWidth={widths[columnIndex]}
|
||||||
|
columnWidths={widths}
|
||||||
isSelected={isCellSelected}
|
isSelected={isCellSelected}
|
||||||
isEditing={isCellEditing}
|
isEditing={isCellEditing}
|
||||||
displayValue={displayValue}
|
displayValue={displayValue}
|
||||||
@@ -312,6 +437,7 @@ const CellRenderer = ({
|
|||||||
stopEditing={stopEditing}
|
stopEditing={stopEditing}
|
||||||
setActiveSheet={setActiveSheet}
|
setActiveSheet={setActiveSheet}
|
||||||
activeCellRef={activeCellInputRef}
|
activeCellRef={activeCellInputRef}
|
||||||
|
mergedCellInfo={mergedCellProps}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -349,6 +475,7 @@ interface SpreadsheetProps {
|
|||||||
colIndex: number,
|
colIndex: number,
|
||||||
newWidth: number,
|
newWidth: number,
|
||||||
) => void
|
) => void
|
||||||
|
mergedCells?: MergedCell[] // Добавляем поддержку объединенных ячеек
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Spreadsheet Component ---
|
// --- Spreadsheet Component ---
|
||||||
@@ -374,6 +501,7 @@ const Spreadsheet = ({
|
|||||||
gridRefs,
|
gridRefs,
|
||||||
onGridScroll,
|
onGridScroll,
|
||||||
handleColumnResize,
|
handleColumnResize,
|
||||||
|
mergedCells,
|
||||||
}: SpreadsheetProps) => {
|
}: SpreadsheetProps) => {
|
||||||
const widths = columnWidths
|
const widths = columnWidths
|
||||||
const cachedData = getCachedCellData(sheetType)
|
const cachedData = getCachedCellData(sheetType)
|
||||||
@@ -386,6 +514,7 @@ const Spreadsheet = ({
|
|||||||
isEditing,
|
isEditing,
|
||||||
editingValue,
|
editingValue,
|
||||||
cachedData,
|
cachedData,
|
||||||
|
mergedCells,
|
||||||
handleCellClick,
|
handleCellClick,
|
||||||
handleCellDoubleClick,
|
handleCellDoubleClick,
|
||||||
handleCellChange,
|
handleCellChange,
|
||||||
@@ -530,7 +659,10 @@ const Spreadsheet = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const DualSpreadsheet: FC<DualSpreadsheetProps> = ({ templateData = {} }) => {
|
const DualSpreadsheet: FC<DualSpreadsheetProps> = ({
|
||||||
|
templateData = {},
|
||||||
|
mergedCells = [],
|
||||||
|
}) => {
|
||||||
const { revision, updateRevision, ...multiSheetEngine } = useMultiSheetEngine(
|
const { revision, updateRevision, ...multiSheetEngine } = useMultiSheetEngine(
|
||||||
{
|
{
|
||||||
sheets: [
|
sheets: [
|
||||||
@@ -996,6 +1128,7 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({ templateData = {} }) => {
|
|||||||
gridRefs={gridRefs}
|
gridRefs={gridRefs}
|
||||||
onGridScroll={onGridScroll}
|
onGridScroll={onGridScroll}
|
||||||
handleColumnResize={handleColumnResize}
|
handleColumnResize={handleColumnResize}
|
||||||
|
mergedCells={mergedCells}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
ref={setSeparatorRef}
|
ref={setSeparatorRef}
|
||||||
@@ -1025,6 +1158,7 @@ const DualSpreadsheet: FC<DualSpreadsheetProps> = ({ templateData = {} }) => {
|
|||||||
gridRefs={gridRefs}
|
gridRefs={gridRefs}
|
||||||
onGridScroll={onGridScroll}
|
onGridScroll={onGridScroll}
|
||||||
handleColumnResize={handleColumnResize}
|
handleColumnResize={handleColumnResize}
|
||||||
|
mergedCells={mergedCells}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -240,7 +240,11 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
|
|||||||
|
|
||||||
{/* Таблицы - теперь занимают всё оставшееся пространство */}
|
{/* Таблицы - теперь занимают всё оставшееся пространство */}
|
||||||
<div className="min-h-0 flex-1">
|
<div className="min-h-0 flex-1">
|
||||||
<DualSpreadsheet key={dataVersion} templateData={{ L: excelData }} />
|
<DualSpreadsheet
|
||||||
|
key={dataVersion}
|
||||||
|
templateData={{ L: excelData }}
|
||||||
|
mergedCells={editedTemplate.mergedCells}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user