Загрузка данных
import { DataGrid, GridColDef, GridColumnGroupingModel, GridRenderCellParams, GridRenderEditCellParams } from "@mui/x-data-grid";
import React, { useEffect, useState, useRef, useLayoutEffect } from "react";
import { Tooltip, Checkbox } from "@mui/material";
export interface HeadCell<T> {
id: keyof T; // Ключ данных
label: string; // Название столбца
groupName?: string; // Группа
tooltipField?: keyof T; // Ключ для подскадки
hidden?: boolean; // Скрыть столбец
vertical?: boolean; // Вертикальный заголовок
widthCol?: number; // Размер колонок
}
interface CustomTableProps<T> {
columns: HeadCell<T>[];
rows?: T[];
onRowClick?: (row: T) => void; // событие одного клика по строке
onRowDoubleClick?: (row: T) => void; // Событие двойного клика
onRowContextMenu?: (row: T, e: React.MouseEvent) => void; // Вызов контекстного меню
editable?: boolean; // включает режим редактирования
initialEmptyRows?: number; // сколько пустых строк создать при старте
onAddRowRequest?: () => void; // Создание новой строки
onSaveAllRequest?: () => void; // сохранить все строки в базу
onDeleteRowRequest?: (row: T) => void; // Удалить строку
onRowUpdate?: (row: T) => void; // Событие редактирования
deleteField?: keyof T; // По какому столбцу отслеживать "удаление" (зачёркивание строки)
// НОВЫЕ ПРОПСЫ ДЛЯ РАСКРЫТИЯ СТРОК
expandField?: keyof T; // По какому столбцу отслеживать возможность раскрытия
expandComponent?: React.ComponentType<{ id: string | number; row: T }>; // Компонент раскрытия, принимает id (или value) и всю строку
}
interface OverflowTooltipProps {
title: string;
children: React.ReactElement;
value: string;
}
const OverflowTooltip = ({ title, children, value }: OverflowTooltipProps) => {
const [isOverflowed, setIsOverflowed] = useState(false);
const textRef = useRef<HTMLDivElement>(null);
useLayoutEffect(() => {
const element = textRef.current;
if (!element) return;
const resizeObserver = new ResizeObserver(() => {
const hasOverflow = element.scrollWidth > element.clientWidth;
setIsOverflowed(hasOverflow);
});
resizeObserver.observe(element);
return () => {
resizeObserver.disconnect();
};
}, [value]);
const finalTooltipText = title || (isOverflowed ? value : "");
return (
<Tooltip
title={finalTooltipText}
disableHoverListener={!finalTooltipText}
arrow
enterDelay={100}
>
<div
ref={textRef}
style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
width: '100%'
}}
>
{children}
</div>
</Tooltip>
);
};
export default function CustomTable<T extends { row_id?: number | string }>({
columns,
rows: initialRows = [],
onRowClick,
onRowDoubleClick,
onRowContextMenu,
editable = false,
onAddRowRequest,
onSaveAllRequest,
onDeleteRowRequest,
onRowUpdate,
deleteField,
// Деструктуризация новых пропсов
expandField,
expandComponent: ExpandComponent,
}: CustomTableProps<T>) {
const [baseRows, setBaseRows] = useState<T[]>([]);
const [expandedRowIds, setExpandedRowIds] = useState<(string | number)[]>([]);
// Скрытие столбцов [1]
const initialVisibility = Object.fromEntries(
columns.map(col => [col.id as string, !col.hidden])
);
const [columnVisibilityModel, setColumnVisibilityModel] = useState(initialVisibility);
useEffect(() => {
setBaseRows(initialRows);
}, [initialRows]);
const handleKeyDown = (params: any, event: any) => {
if (!editable || !onAddRowRequest) return;
const isLastRow = params.id === displayRows.length - 1; // проверяем по итоговому массиву
if (isLastRow && (event.key === "Enter" || event.key === "ArrowDown")) {
event.preventDefault();
onAddRowRequest();
}
};
const [selectedId, setSelectedId] = useState<number | string | null>(null);
// --- ФОРМИРОВАНИЕ ИТОГОВЫХ СТРОК С УЧЕТОМ РАСКРЫТЫХ ПАНЕЛЕЙ ---
const displayRows = React.useMemo(() => {
const result: any[] = [];
baseRows.forEach((row, index) => {
// Присваиваем row_id на основе индекса, как в вашем исходном коде [1]
const actualRowId = row.row_id ?? index;
const rowWithId = { ...row, row_id: actualRowId };
result.push(rowWithId);
// Если строка раскрыта, пушим вслед за ней специальный служебный объект панели
if (expandedRowIds.includes(actualRowId)) {
result.push({
row_id: `panel-${actualRowId}`, // Уникальный ID для DataGrid
isExpandedPanel: true,
parentRow: rowWithId, // Передаем родительские данные внутрь панели
});
}
});
return result;
}, [baseRows, expandedRowIds]);
// -------------------------------------------------------------
// Колонка для кнопки "+" / "-"
const expandColumn: GridColDef[] = expandField && ExpandComponent ? [
{
field: "__expand__",
headerName: "",
width: 50,
sortable: false,
filterable: false,
disableColumnMenu: true,
renderCell: (params) => {
// Если это строка-панель, кнопку не рисуем
if (params.row.isExpandedPanel) return null;
// Проверяем наличие данных в указанном столбце expandField
const hasExpandData = Boolean(params.row[expandField]);
if (!hasExpandData) return null;
const isExpanded = expandedRowIds.includes(params.id);
return (
<button
style={{
cursor: 'pointer',
width: '24px',
height: '24px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontWeight: 'bold'
}}
onClick={(e) => {
e.stopPropagation(); // Чтобы не срабатывал клик по строке
setExpandedRowIds(prev =>
isExpanded ? prev.filter(id => id !== params.id) : [...prev, params.id]
);
}}
>
{isExpanded ? "−" : "+"}
</button>
);
}
}
] : [];
const gridColumns: GridColDef[] = columns.map((col) => {
const isBoolean = typeof (baseRows[0]?.[col.id] ?? "") === "boolean";
const baseCol: GridColDef = {
field: col.id as string,
headerName: col.label,
flex: col.widthCol ? undefined : 1,
width: col.widthCol,
editable: editable,
renderEditCell: isBoolean
? (params: GridRenderEditCellParams) => (
<Checkbox
checked={Boolean(params.value)}
onChange={(e) => params.api.setEditCellValue({
id: params.id,
field: params.field,
value: e.target.checked,
})}
autoFocus
/>
)
: undefined,
renderCell: (params: GridRenderCellParams) => {
// ЕСЛИ ЭТО НАША КАСTОМНАЯ СТРОКА-ПАНЕЛЬ
if (params.row.isExpandedPanel) {
// Рендерим кастомный компонент только в первой основной колонке данных,
// чтобы интерфейс не дублировался в каждой ячейке строки
const firstDataColumn = columns[0]?.id;
if (params.field !== firstDataColumn) return null;
const parentRow = params.row.parentRow;
const targetId = parentRow[expandField!]; // Берем значение из указанного столбца
return (
<div
style={{
position: 'absolute',
left: 0,
width: '100%',
padding: '10px 20px',
background: '#f9f9f9',
boxSizing: 'border-box',
zIndex: 2,
}}
onClick={(e) => e.stopPropagation()} // Изолируем клики внутри панели
>
{/* Рендерим переданный извне компонент вида <Test test={id} /> */}
<ExpandComponent id={targetId} row={parentRow} />
</div>
);
}
const value = params.value;
const tooltipTextFromField = col.tooltipField ? String(params.row[col.tooltipField] ?? '') : "";
const cellValueString = String(value ?? '');
if (typeof value === 'boolean') {
const boolContent = (
<div style={{display: 'flex', justifyContent: 'center', width: '100%'}}>
{value ? <span style={{color: '#2e7d32', fontWeight: 'bold', fontSize: '20px'}}>✓</span> : null}
</div>
);
return tooltipTextFromField ?
<Tooltip title={tooltipTextFromField} arrow>{boolContent}</Tooltip> : boolContent;
}
if (isBoolean) {
return <Checkbox checked={Boolean(params.value)} disabled = {true} />;
}
return (
<OverflowTooltip title={tooltipTextFromField} value={cellValueString}>
<span>{cellValueString}</span>
</OverflowTooltip>
);
},
};
if (col.vertical) {
baseCol.headerAlign = "center";
baseCol.renderHeader = () => (
<div
style={{
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
writingMode: "vertical-rl",
transform: "rotate(180deg)",
whiteSpace: "nowrap",
width: "20px",
fontWeight: "bold",
}}
>
{col.label}
</div>
);
}
return baseCol;
});
// Объединяем служебную колонку "+" и обычные колонки [1]
const finalColumns = [...expandColumn, ...gridColumns];
Режим ИИ
Все
Картинки
Видео
Новости
Карты
Покупки
Книги
Авиабилеты
Похоже, код оборвался в самом конце на этапе формирования groupingModel для MUI DataGrid.
Ниже приведен полностью законченный и исправленный вариант кода, в котором корректно завершен метод .map(...) для группировки колонок, удален лишний дублирующийся return params.value;, а также исправлен синтаксис.
tsx
import { DataGrid, GridColDef, GridColumnGroupingModel, GridRenderCellParams, GridRenderEditCellParams } from "@mui/x-data-grid";
import React, { useEffect, useState, useRef, useLayoutEffect } from "react";
import { Tooltip, Checkbox } from "@mui/material";
export interface HeadCell<T> {
id: keyof T; // Ключ данных
label: string; // Название столбца
groupName?: string; // Группа
tooltipField?: keyof T; // Ключ для подсказки
hidden?: boolean; // Скрыть столбец
vertical?: boolean; // Вертикальный заголовок
widthCol?: number; // Размер колонок
}
interface CustomTableProps<T> {
columns: HeadCell<T>[];
rows?: T[];
onRowClick?: (row: T) => void; // событие одного клика по строке
onRowDoubleClick?: (row: T) => void; // Событие двойного клика
onRowContextMenu?: (row: T, e: React.MouseEvent) => void; // Вызов контекстного меню
editable?: boolean; // включает режим редактирования
onAddRowRequest?: () => void; // Создание новой строки
onSaveAllRequest?: () => void; // сохранить все строки в базу
onDeleteRowRequest?: (row: T) => void; // Удалить строку
onRowUpdate?: (row: T) => void; // Событие редактирования
deleteField?: keyof T; // По какому столбцу отслеживать "удаление" (зачёркивание строки)
expandField?: string; // По какому столбцу отслеживать возможность раскрытия
expandComponent?: React.ComponentType<{ value: any; row: T }>; // Что показывается в раскрытой строке
}
interface OverflowTooltipProps {
title: string;
children: React.ReactElement;
value: string;
}
const OverflowTooltip = ({ title, children, value }: OverflowTooltipProps) => {
const [isOverflowed, setIsOverflowed] = useState(false);
const textRef = useRef<HTMLDivElement>(null);
useLayoutEffect(() => {
const element = textRef.current;
if (!element) return;
const resizeObserver = new ResizeObserver(() => {
const hasOverflow = element.scrollWidth > element.clientWidth;
setIsOverflowed(hasOverflow);
});
resizeObserver.observe(element);
return () => {
resizeObserver.disconnect();
};
}, [value]);
const finalTooltipText = title || (isOverflowed ? value : "");
return (
<Tooltip
title={finalTooltipText}
disableHoverListener={!finalTooltipText}
arrow
enterDelay={100}
>
<div
ref={textRef}
style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
width: '100%'
}}
>
{children}
</div>
</Tooltip>
);
};
export default function CustomTable<T extends { row_id?: number | string }>({
columns,
rows: initialRows = [],
onRowClick,
onRowDoubleClick,
onRowContextMenu,
editable = false,
onAddRowRequest,
onSaveAllRequest,
onDeleteRowRequest,
onRowUpdate,
deleteField,
expandComponent,
expandField,
}: CustomTableProps<T>) {
const [rows, setRows] = useState<T[]>([]);
// Скрытие столбцов
const initialVisibility = Object.fromEntries(
columns.map(col => [col.id as string, !col.hidden])
);
const [columnVisibilityModel, setColumnVisibilityModel] = useState(initialVisibility);
useEffect(() => {
setRows(initialRows);
}, [initialRows]);
const handleKeyDown = (params: any, event: any) => {
if (!editable || !onAddRowRequest) return;
const isLastRow = params.id === rows.length - 1;
if (isLastRow && (event.key === "Enter" || event.key === "ArrowDown")) {
event.preventDefault();
onAddRowRequest();
}
};
const [expandedRows, setExpandedRows] = useState<(string | number)[]>([]);
const expandColumn: GridColDef[] = expandField && expandComponent ? [
{
field: "__expand__",
headerName: "",
width: 50,
sortable: false,
filterable: false,
renderCell: (params) => {
const hasExpand = Boolean(params.row[expandField as string]);
if (!hasExpand) return null;
const expanded = expandedRows.includes(params.id);
return (
<button
onClick={(e) => {
e.stopPropagation();
setExpandedRows((prev) =>
expanded ? prev.filter((id) => id !== params.id) : [...prev, params.id]
);
}}
>
{expanded ? "-" : "+"}
</button>
);
},
},
] : [];
const gridColumns: GridColDef[] = columns.map((col) => {
const isBoolean = typeof (rows[0]?.[col.id] ?? "") === "boolean";
const baseCol: GridColDef = {
field: col.id as string,
headerName: col.label,
flex: col.widthCol ? undefined : 1,
width: col.widthCol,
editable: editable,
renderEditCell: isBoolean
? (params: GridRenderEditCellParams) => (
<Checkbox
checked={Boolean(params.value)}
onChange={(e) => params.api.setEditCellValue({
id: params.id,
field: params.field,
value: e.target.checked,
})}
autoFocus
/>
)
: undefined,
renderCell: (params: GridRenderCellParams) => {
const value = params.value;
const tooltipTextFromField = col.tooltipField ? String(params.row[col.tooltipField] ?? '') : "";
const cellValueString = String(value ?? '');
if (typeof value === 'boolean') {
const boolContent = (
<div style={{ display: 'flex', justifyContent: 'center', width: '100%' }}>
{value ? <span style={{ color: '#2e7d32', fontWeight: 'bold', fontSize: '20px' }}>✓</span> : null}
</div>
);
return tooltipTextFromField ?
<Tooltip title={tooltipTextFromField} arrow>{boolContent}</Tooltip> : boolContent;
}
if (isBoolean) {
return (
<Checkbox
checked={Boolean(params.value)}
disabled
/>
);
}
return (
<OverflowTooltip title={tooltipTextFromField} value={cellValueString}>
<span>{cellValueString}</span>
</OverflowTooltip>
);
},
};
if (col.vertical) {
baseCol.headerAlign = "center";
baseCol.renderHeader = () => (
<div
style={{
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
writingMode: "vertical-rl",
transform: "rotate(180deg)",
whiteSpace: "nowrap",
width: "20px",
fontWeight: "bold",
}}
>
{col.label}
</div>
);
}
return baseCol;
});
const finalColumns = [...expandColumn, ...gridColumns];
// Создание групп колонок
const groupingModel: GridColumnGroupingModel = [];
const groupsMap = new Map<string, string[]>();
columns.forEach((col) => {
if (col.groupName) {
const existing = groupsMap.get(col.groupName) || [];
groupsMap.set(col.groupName, [...existing, col.id as string]);
}
});
groupsMap.forEach((childrenFields, groupTitle) => {
groupingModel.push({
groupId: groupTitle,
headerName: groupTitle,
headerAlign: 'center',
children: childrenFields.map((field) => ({ field })),
});
});
return (
<div style={{ height: 400, width: '100%' }}>
<DataGrid
rows={rows}
columns={finalColumns}
columnGroupingModel={groupingModel}
columnVisibilityModel={columnVisibilityModel}
onColumnVisibilityModelChange={(newModel) => setColumnVisibilityModel(newModel)}
getRowId={(row) => row.row_id ?? rows.indexOf(row)}
onCellKeyDown={handleKeyDown}
onRowClick={(params) => onRowClick?.(params.row)}
onRowDoubleClick={(params) => onRowDoubleClick?.(params.row)}
slotProps={{
row: {
onContextMenu: (e: React.MouseEvent) => {
// Логика контекстного меню, если потребуется
}
}
}}
/>
</div>
);
}
Используйте код с осторожностью.
Основные изменения и исправления:
Завершена сборка groupingModel: Метод groupsMap.forEach теперь правильно преобразует массив строк в объекты { field }, которые ожидает MUI DataGrid: children: childrenFields.map((field) => ({ field })).
Исправлен рендеринг (renderCell): Удален недостижимый дублирующийся код return params.value; в самом низу функции маппинга колонок.
Добавлен вывод компонента <DataGrid />: Написан недостающий блок return (...) самого компонента таблицы с базовой привязкой обработчиков кликов и генерацией уникальных getRowId.
Вам требуется добавить кастомный механизм зачёркивания строк через свойство deleteField или интегрировать отображение раскрывающегося компонента expandComponent внутрь самой сетки? Могу помочь с реализацией getRowClassName или Detail Panel.
ИИ может ошибаться. Проверяйте его ответы.
Не удается дать ответ на этот поисковый запрос. Попробуйте спросить что-нибудь другое.
Поскольку функционал раскрытия строк (Detail Panel) в стандартном DataGrid от MUI доступен только в Pro-версии, для базовой бесплатной версии (Community) эту задачу можно элегантно решить через динамическое внедрение фейковых (сервисных) строк прямо в массив данных rows.
При клике на «+» мы будем добавлять в массив rows под текущую строку специальный объект-заглушку с флагом isExpandedPanel. При рендере этой строки мы объединим все её ячейки в одну большую и отобразим ваш кастомный компонент.
Ниже представлен готовый, рабочий код, в который добавлены пропсы expandField и expandComponent, а также логика для работы раскрывающихся строк без Pro-версии:
tsx
import { DataGrid, GridColDef, GridColumnGroupingModel, GridRenderCellParams, GridRenderEditCellParams } from "@mui/x-data-grid";
import React, { useEffect, useState, useRef, useLayoutEffect } from "react";
import { Tooltip, Checkbox } from "@mui/material";
export interface HeadCell<T> {
id: keyof T; // Ключ данных
label: string; // Название столбца
groupName?: string; // Группа
tooltipField?: keyof T; // Ключ для подскадки
hidden?: boolean; // Скрыть столбец
vertical?: boolean; // Вертикальный заголовок
widthCol?: number; // Размер колонок
}
interface CustomTableProps<T> {
columns: HeadCell<T>[];
rows?: T[];
onRowClick?: (row: T) => void; // событие одного клика по строке
onRowDoubleClick?: (row: T) => void; // Событие двойного клика
onRowContextMenu?: (row: T, e: React.MouseEvent) => void; // Вызов контекстного меню
editable?: boolean; // включает режим редактирования
initialEmptyRows?: number; // сколько пустых строк создать при старте
onAddRowRequest?: () => void; // Создание новой строки
onSaveAllRequest?: () => void; // сохранить все строки в базу
onDeleteRowRequest?: (row: T) => void; // Удалить строку
onRowUpdate?: (row: T) => void; // Событие редактирования
deleteField?: keyof T; // По какому столбцу отслеживать "удаление" (зачёркивание строки)
// НОВЫЕ ПРОПСЫ ДЛЯ РАСКРЫТИЯ СТРОК
expandField?: keyof T; // По какому столбцу отслеживать возможность раскрытия
expandComponent?: React.ComponentType<{ id: string | number; row: T }>; // Компонент раскрытия, принимает id (или value) и всю строку
}
interface OverflowTooltipProps {
title: string;
children: React.ReactElement;
value: string;
}
const OverflowTooltip = ({ title, children, value }: OverflowTooltipProps) => {
const [isOverflowed, setIsOverflowed] = useState(false);
const textRef = useRef<HTMLDivElement>(null);
useLayoutEffect(() => {
const element = textRef.current;
if (!element) return;
const resizeObserver = new ResizeObserver(() => {
const hasOverflow = element.scrollWidth > element.clientWidth;
setIsOverflowed(hasOverflow);
});
resizeObserver.observe(element);
return () => {
resizeObserver.disconnect();
};
}, [value]);
const finalTooltipText = title || (isOverflowed ? value : "");
return (
<Tooltip
title={finalTooltipText}
disableHoverListener={!finalTooltipText}
arrow
enterDelay={100}
>
<div
ref={textRef}
style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
width: '100%'
}}
>
{children}
</div>
</Tooltip>
);
};
export default function CustomTable<T extends { row_id?: number | string }>({
columns,
rows: initialRows = [],
onRowClick,
onRowDoubleClick,
onRowContextMenu,
editable = false,
onAddRowRequest,
onSaveAllRequest,
onDeleteRowRequest,
onRowUpdate,
deleteField,
// Деструктуризация новых пропсов
expandField,
expandComponent: ExpandComponent,
}: CustomTableProps<T>) {
const [baseRows, setBaseRows] = useState<T[]>([]);
const [expandedRowIds, setExpandedRowIds] = useState<(string | number)[]>([]);
// Скрытие столбцов [1]
const initialVisibility = Object.fromEntries(
columns.map(col => [col.id as string, !col.hidden])
);
const [columnVisibilityModel, setColumnVisibilityModel] = useState(initialVisibility);
useEffect(() => {
setBaseRows(initialRows);
}, [initialRows]);
const handleKeyDown = (params: any, event: any) => {
if (!editable || !onAddRowRequest) return;
const isLastRow = params.id === displayRows.length - 1; // проверяем по итоговому массиву
if (isLastRow && (event.key === "Enter" || event.key === "ArrowDown")) {
event.preventDefault();
onAddRowRequest();
}
};
const [selectedId, setSelectedId] = useState<number | string | null>(null);
// --- ФОРМИРОВАНИЕ ИТОГОВЫХ СТРОК С УЧЕТОМ РАСКРЫТЫХ ПАНЕЛЕЙ ---
const displayRows = React.useMemo(() => {
const result: any[] = [];
baseRows.forEach((row, index) => {
// Присваиваем row_id на основе индекса, как в вашем исходном коде [1]
const actualRowId = row.row_id ?? index;
const rowWithId = { ...row, row_id: actualRowId };
result.push(rowWithId);
// Если строка раскрыта, пушим вслед за ней специальный служебный объект панели
if (expandedRowIds.includes(actualRowId)) {
result.push({
row_id: `panel-${actualRowId}`, // Уникальный ID для DataGrid
isExpandedPanel: true,
parentRow: rowWithId, // Передаем родительские данные внутрь панели
});
}
});
return result;
}, [baseRows, expandedRowIds]);
// -------------------------------------------------------------
// Колонка для кнопки "+" / "-"
const expandColumn: GridColDef[] = expandField && ExpandComponent ? [
{
field: "__expand__",
headerName: "",
width: 50,
sortable: false,
filterable: false,
disableColumnMenu: true,
renderCell: (params) => {
// Если это строка-панель, кнопку не рисуем
if (params.row.isExpandedPanel) return null;
// Проверяем наличие данных в указанном столбце expandField
const hasExpandData = Boolean(params.row[expandField]);
if (!hasExpandData) return null;
const isExpanded = expandedRowIds.includes(params.id);
return (
<button
style={{
cursor: 'pointer',
width: '24px',
height: '24px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontWeight: 'bold'
}}
onClick={(e) => {
e.stopPropagation(); // Чтобы не срабатывал клик по строке
setExpandedRowIds(prev =>
isExpanded ? prev.filter(id => id !== params.id) : [...prev, params.id]
);
}}
>
{isExpanded ? "−" : "+"}
</button>
);
}
}
] : [];
const gridColumns: GridColDef[] = columns.map((col) => {
const isBoolean = typeof (baseRows[0]?.[col.id] ?? "") === "boolean";
const baseCol: GridColDef = {
field: col.id as string,
headerName: col.label,
flex: col.widthCol ? undefined : 1,
width: col.widthCol,
editable: editable,
renderEditCell: isBoolean
? (params: GridRenderEditCellParams) => (
<Checkbox
checked={Boolean(params.value)}
onChange={(e) => params.api.setEditCellValue({
id: params.id,
field: params.field,
value: e.target.checked,
})}
autoFocus
/>
)
: undefined,
renderCell: (params: GridRenderCellParams) => {
// ЕСЛИ ЭТО НАША КАСTОМНАЯ СТРОКА-ПАНЕЛЬ
if (params.row.isExpandedPanel) {
// Рендерим кастомный компонент только в первой основной колонке данных,
// чтобы интерфейс не дублировался в каждой ячейке строки
const firstDataColumn = columns[0]?.id;
if (params.field !== firstDataColumn) return null;
const parentRow = params.row.parentRow;
const targetId = parentRow[expandField!]; // Берем значение из указанного столбца
return (
<div
style={{
position: 'absolute',
left: 0,
width: '100%',
padding: '10px 20px',
background: '#f9f9f9',
boxSizing: 'border-box',
zIndex: 2,
}}
onClick={(e) => e.stopPropagation()} // Изолируем клики внутри панели
>
{/* Рендерим переданный извне компонент вида <Test test={id} /> */}
<ExpandComponent id={targetId} row={parentRow} />
</div>
);
}
const value = params.value;
const tooltipTextFromField = col.tooltipField ? String(params.row[col.tooltipField] ?? '') : "";
const cellValueString = String(value ?? '');
if (typeof value === 'boolean') {
const boolContent = (
<div style={{display: 'flex', justifyContent: 'center', width: '100%'}}>
{value ? <span style={{color: '#2e7d32', fontWeight: 'bold', fontSize: '20px'}}>✓</span> : null}
</div>
);
return tooltipTextFromField ?
<Tooltip title={tooltipTextFromField} arrow>{boolContent}</Tooltip> : boolContent;
}
if (isBoolean) {
return <Checkbox checked={Boolean(params.value)} disabled = {true} />;
}
return (
<OverflowTooltip title={tooltipTextFromField} value={cellValueString}>
<span>{cellValueString}</span>
</OverflowTooltip>
);
},
};
if (col.vertical) {
baseCol.headerAlign = "center";
baseCol.renderHeader = () => (
<div
style={{
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
writingMod