합계 및 요약행 (Summary Row)
AXDGProps의 summary 설정으로 상단 또는 하단에 사용자 정의 요약 셀을 표시하는 방법을 학습합니다.
import { Divider, Radio } from 'antd';
import { useEffect, useState } from 'react';
import * as React from 'react';
import { AXDataGrid, AXDGColumn, AXDGProps } from '@axboot/datagrid';
import { useContainerSize } from '../hooks/useContainerSize';
import DataGridContainer from '../components/DataGridContainer';
interface Props {}
interface IListItem {
id?: string;
class?: string;
title?: string;
writer?: string;
createAt?: string;
}
const list = Array.from(Array(50)).map((v, i) => ({
values: {
id: `ID_${i}`,
class: `class_${Math.floor(i / 5)}`,
title: `title_${i}`,
writer: `writer_${i}`,
createAt: `2022-09-08`,
},
}));
const summary1: AXDGProps<any>['summary'] = {
position: 'top',
columns: [
{
columnIndex: 0,
align: 'center',
itemRender: ({ data }) => {
return <>합계</>;
},
},
{
columnIndex: 2,
itemRender: ({ data }) => {
return <>Summary</>;
},
},
{
columnIndex: 3,
colSpan: 2,
itemRender: ({ data }) => {
return <>COUNT : {data.length}</>;
},
},
],
};
const summary2: AXDGProps<any>['summary'] = {
position: 'bottom',
columns: [
{
columnIndex: 0,
align: 'center',
itemRender: ({ data }) => {
return <>합계</>;
},
},
{
columnIndex: 2,
colSpan: 2,
align: 'right',
itemRender: ({ data }) => {
return <>Summary</>;
},
},
{
columnIndex: 5,
colSpan: 2,
itemRender: ({ data }) => {
return <>Summary</>;
},
},
],
};
function SummaryExample(props: Props) {
const [summaryKey, setSummaryKey] = useState('summary1');
const [summary, setSummary] = useState<AXDGProps<any>['summary']>();
const [columns, setColumns] = React.useState<AXDGColumn<IListItem>[]>([
{
key: 'class',
label: 'class',
width: 100,
align: 'center',
},
{
key: 'id',
label: 'No',
width: 100,
},
{
key: 'title',
label: 'Title',
width: 300,
itemRender: ({ values }) => {
return (
<>
{values.writer} / {values.title}
</>
);
},
},
{
key: 'writer',
label: 'Writer',
width: 100,
itemRender: ({ values: values }) => {
return <>{values.writer} / A</>;
},
},
{
key: 'createAt',
label: 'Date-A',
width: 100,
align: 'center',
},
{
key: 'createAt',
label: 'Date-B',
width: 100,
align: 'center',
},
{
key: 'createAt',
label: 'Date-C',
width: 100,
align: 'center',
},
{
key: 'createAt',
label: 'Date-D',
width: 100,
},
{
key: 'createAt',
label: 'Date-E',
width: 100,
},
]);
const containerRef = React.useRef<HTMLDivElement>(null);
const { width: containerWidth, height: containerHeight } = useContainerSize(containerRef);
useEffect(() => {
setSummary(summaryKey === 'summary1' ? summary1 : summary2);
}, [summaryKey]);
return (
<>
<Radio.Group
style={{ marginBottom: 10 }}
options={[
{ label: 'TOP', value: 'summary1' },
{ label: 'Bottom', value: 'summary2' },
]}
onChange={e => {
setSummaryKey(e.target.value);
}}
value={summaryKey}
/>
<DataGridContainer ref={containerRef}>
<AXDataGrid<IListItem>
showLineNumber
rowChecked={{
checkedIndexes: [],
onChange: (ids, selectedAll) => {
console.log('onChange rowSelection', ids, selectedAll);
},
}}
frozenColumnIndex={1}
width={containerWidth}
height={containerHeight}
data={list}
columns={columns}
onChangeColumns={(columnIndex, { width, columns }) => {
console.log('onChangeColumnWidths', columnIndex, width, columns);
setColumns(columns);
}}
cellMergeOptions={{
columnsMap: {
0: { mergeBy: 'class' },
4: { mergeBy: 'class' },
5: { mergeBy: 'class' },
6: { mergeBy: 'class' },
},
}}
onClick={item => console.log(item)}
variant={'vertical-bordered'}
summary={summary}
/>
</DataGridContainer>
</>
);
}
export default SummaryExample;import * as React from 'react';
import './DataGridContainer.css';
interface DataGridContainerProps extends React.HTMLAttributes<HTMLDivElement> {
children?: React.ReactNode;
}
/**
* Keeps a DataGrid in a measured, fixed layout box.
*
* AXDataGrid's rendered root is absolutely positioned within this relative
* container. This makes a ResizeObserver measurement authoritative when a
* surrounding flex or grid layout shrinks as well as when it expands.
*/
const DataGridContainer = React.forwardRef<HTMLDivElement, DataGridContainerProps>(
({ className, ...rest }, ref) => (
<div ref={ref} className={`data-grid-container ${className ?? ''}`.trim()} {...rest} />
),
);
DataGridContainer.displayName = 'DataGridContainer';
export default DataGridContainer;.data-grid-container {
position: relative;
width: 100%;
height: 400px;
overflow: hidden;
font-size: 13px;
}
.data-grid-container > .axdg-root {
position: absolute;
inset: 0;
}import * as React from 'react';
export function useContainerSize(ref: React.MutableRefObject<HTMLElement | null>, additionalDeps: unknown[] = []) {
const [width, setWidth] = React.useState(0);
const [height, setHeight] = React.useState(0);
const resizeObserver = React.useRef(
new ResizeObserver(entries => {
if (entries.length !== 1) {
throw new Error('Invalid Container length');
}
const [entry] = entries;
const { width, height } = entry.contentRect;
setWidth(width);
setHeight(height);
}),
);
React.useEffect(() => {
if (!ref.current) return;
const observer = resizeObserver.current;
const element = ref.current;
setWidth(element.clientWidth);
setHeight(element.clientHeight);
observer.observe(element);
return () => {
observer.unobserve(element);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [...additionalDeps, ref]);
return {
width,
height,
};
}Summary 설정
summary는 일반 <table>이나 <tr>을 직접 반환하는 렌더 prop이 아닙니다. 표시 위치와 요약할 컬럼 정의 배열을 전달합니다.
const summary: AXDGProps<Row>['summary'] = {
position: 'bottom',
columns: [
{
columnIndex: 0,
colSpan: 2,
align: 'center',
itemRender: () => <>합계</>,
},
{
columnIndex: 2,
align: 'right',
itemRender: ({ data }) => (
<>{data.reduce((sum, item) => sum + item.values.amount, 0).toLocaleString()}</>
),
},
],
};
<AXDataGrid summary={summary} summaryHeight={32} {...props} />
멤버 의미
| 멤버 | 설명 |
|---|---|
position |
요약 행을 데이터 위(top) 또는 아래(bottom)에 배치합니다. |
columnIndex |
요약 셀이 시작될 컬럼의 0 기반 인덱스입니다. |
colSpan |
요약 셀이 차지할 컬럼 수입니다. |
itemRender |
{ column, columnIndex, data }를 받아 요약 콘텐츠를 반환합니다. |
summaryHeight |
요약 영역의 높이를 조정하는 최상위 prop입니다. |
data는 AXDGDataItem<T>[]이므로 실제 값은 item.values에서 읽습니다. 페이지 단위 합계인지 전체 데이터 합계인지는 부모가 그리드에 어떤 데이터를 전달했는지에 따라 결정됩니다.