컬럼 순서 재배치 (Column Reorder)

마우스 드래그 앤 드롭으로 컬럼 헤더의 순서를 자유롭게 변경하고 변경된 순서를 저장하는 방법을 학습합니다.

#columnSortable#drag-and-drop#reorder#user-customization
검토일: 2026-08-17
GitHub
import * as React from 'react';
import {
  AXDataGrid,
  AXDGColumn,
  AXDGColumnGroup,
  AXDGDataItem,
  AXDGItemRenderProps,
  AXDGSortParam,
  toMoney,
} from '@axboot/datagrid';
import { useContainerSize } from '../hooks/useContainerSize';
import DataGridContainer from '../components/DataGridContainer';
import { Progress } from 'antd';
import { useState } from 'react';

interface Props {}

interface IListItem {
  nation: string;
  awpc: number;
  wpc: number;
  man: number;
  woman: number;
  ratio: number;
  ratioMan: number;
  ratioWoman: number;
}

const rawData = [
  ['대한민국(15+ LFS)', 44504, 28186, 16090, 12097, 63, 73, 53],
  ['아르메니아(15~75 LFS)', 2204, 1563, 780, 783, 70, 76, 66],
  ['아제르바이잔(15+ LFS)', 0, 5190, 2664, 2526, 66, 69, 63],
  ['부탄(15+ LFS)', 482, 320, 169, 151, 66, 71, 61],
  ['브루나이(15+ LFS)', 370, 238, 144, 94, 64, 72, 54],
  ['캄보디아(15+ LFS)', 11515, 8756, 4481, 4274, 76, 82, 69],
  ['키프로스(15+ LFS)', 712, 448, 236, 212, 63, 68, 57],
  ['조지아(15+ LFS)', 3037, 1911, 1025, 886, 62, 72, 54],
  ['홍콩(15+ LFS)', 6573, 3988, 1990, 1998, 60, 67, 55],
  ['인도네시아(15+ LFS)', 200485, 136808, 82760, 54048, 68, 82, 53],
  ['이란(15+ LFS)', 61658, 26940, 21707, 5233, 43, 70, 17],
  ['이스라엘(15+ LFS)', 6494, 4124, 2149, 1974, 63, 67, 59],
  ['일본(15+ LFS)', 110271, 68377, 37997, 30380, 62, 71, 53],
  ['카자흐스탄(15+ LFS)', 13131, 9203, 0, 0, 70, 0, 0],
  ['키르기스스탄(15+ LFS)', 4288, 2755, 1620, 1136, 64, 77, 51],
  ['레바논(15+ LFS)', 3677, 1798, 1230, 567, 48, 70, 29],
  ['마카오(16+ LFS)', 0, 395, 193, 202, 0, 74, 66],
  ['말레이시아(15~64 LFS)', 22685, 15582, 9503, 6078, 68, 80, 55],
  ['몰디브(15+ HIES)', 317, 202, 116, 86, 63, 78, 50],
  ['몽골(15+ LFS)', 2106, 1326, 706, 620, 63, 70, 55],
  ['파키스탄(15+ LFS)', 120220, 62030, 47845, 14185, 51, 79, 23],
  ['필리핀(15+ LFS )', 73008, 43399, 26527, 16872, 59, 72, 46],
  ['카타르(15+ LFS)', 2393, 2108, 1823, 285, 88, 95, 57],
  ['사우디아라비아(15+ LFS)', 0, 0, 0, 0, 57, 80, 24],
  ['싱가포르(15+ LFS)', 3422, 2329, 1251, 1077, 68, 75, 61],
  ['스리랑카(15+ LFS)', 16424, 8581, 5550, 3032, 52, 72, 34],
  ['대만(15+ LFS)', 20188, 11946, 6631, 5315, 59, 67, 51],
  ['태국(15+ LFS)', 56575, 37885, 20611, 17274, 67, 75, 59],
  ['튀르키예(15+ LFS)', 61468, 32524, 21855, 10669, 52, 72, 34],
  ['아랍에미리트(15+ LFS)', 9432, 7565, 5693, 1871, 80, 92, 57],
  ['베트남(15+ LFS)', 73394, 55507, 29068, 26440, 75, 81, 70],
];

const list = rawData.map((data, index) => {
  return {
    values: {
      nation: data[0],
      awpc: data[1],
      wpc: data[2],
      man: data[3],
      woman: data[4],
      ratio: data[5],
      ratioMan: data[5],
      ratioWoman: data[5],
    },
  };
});

const numRender = (item: AXDGItemRenderProps<IListItem>) => <>{toMoney(item.value)}</>;

function SortExample(props: Props) {
  const [sortParams, setSortParams] = useState<AXDGSortParam[]>([]);
  const [columns, setColumns] = useState<AXDGColumn<IListItem>[]>([
    {
      key: 'nation',
      label: 'Nation',
      width: 150,
    },
    {
      key: 'awpc',
      label: 'active population',
      width: 150,
      align: 'right',
      itemRender: numRender,
    },
    { key: 'wpc', label: 'population', width: 100, align: 'right', itemRender: numRender },
    { key: 'man', label: 'Man', width: 100, align: 'right', itemRender: numRender },
    { key: 'woman', label: 'Woman', width: 100, align: 'right', itemRender: numRender },
    {
      key: 'ratio',
      label: 'Ratio',
      width: 150,
      align: 'right',
      itemRender: item => {
        return <Progress size={'small'} percent={item.values.ratio} style={{ margin: 0 }} />;
      },
    },
    { key: 'ratioMan', label: 'Man', width: 100, align: 'right' },
    { key: 'ratioWoman', label: 'Woman', width: 100, align: 'right' },
  ]);
  const [columnsGroup, setColumnsGroup] = useState<AXDGColumnGroup[] | undefined>([
    { label: 'Group', groupStartIndex: 2, groupEndIndex: 4, align: 'center' },
  ]);

  const containerRef = React.useRef<HTMLDivElement>(null);
  const { width: containerWidth, height: containerHeight } = useContainerSize(containerRef);

  const sortedList = React.useMemo(() => {
    let i = 0,
      l = sortParams.length;

    return list
      .sort((a, b) => {
        for (i = 0; i < l; i++) {
          const sortInfo = sortParams[i];
          if (sortInfo.key === undefined) {
            continue;
          }

          let valueA = a.values[sortInfo.key as keyof IListItem],
            valueB = b.values[sortInfo.key as keyof IListItem];

          if (typeof valueA !== typeof valueB) {
            valueA = '' + valueA;
            valueB = '' + valueB;
          }
          if (valueA < valueB) {
            return sortInfo.orderBy === 'asc' ? -1 : 1;
          } else if (valueA > valueB) {
            return sortInfo.orderBy === 'asc' ? 1 : -1;
          }
        }

        return 0;
      })
      .slice() as AXDGDataItem<IListItem>[];
  }, [sortParams]);

  return (
    <DataGridContainer ref={containerRef}>
      <AXDataGrid<IListItem>
        width={containerWidth}
        height={containerHeight}
        headerHeight={48}
        data={sortedList}
        columns={columns}
        columnsGroup={columnsGroup}
        columnSortable
        onChangeColumns={(columnIndex, { columns, columnsGroup }) => {
          console.log('onChangeColumnWidths', columnIndex, columns, columnsGroup);
          setColumns(columns);
          setColumnsGroup(columnsGroup);
        }}
        rowChecked={{
          checkedIndexes: [],
          onChange: (ids, selectedAll) => {
            console.log('onChange rowSelection', ids, selectedAll);
          },
        }}
        sort={{
          sortParams,
          onChange: sortParams => {
            console.log('onChange: sortParams', sortParams);
            setSortParams(sortParams);
          },
        }}
        showLineNumber
      />
    </DataGridContainer>
  );
}

export default SortExample;

1. 언제 사용하며 왜 필요한가요?

사용자마다 업무 시 자주 확인하는 컬럼이 다릅니다. 어떤 담당자는 “연락처”를 먼저 보고 싶고, 어떤 담당자는 “주문금액”을 먼저 보고 싶어 합니다.

columnSortable={true}를 설정하면 사용자가 헤더를 마우스로 드래그하여 자신이 원하는 순서로 컬럼 위치를 즉시 바꿀 수 있습니다.


2. 실무 완성형 예제: 드래그 컬럼 순서 변경

import React, { useState } from 'react';
import { AXDataGrid, type AXDGColumn, type AXDGDataItem } from '@axboot/datagrid';

interface TaskItem {
  id: number;
  title: string;
  assignee: string;
  priority: string;
  dueDate: string;
}

export default function ColumnReorderGrid() {
  const [columns, setColumns] = useState<AXDGColumn<TaskItem>[]>([
    { key: 'id', label: 'ID', width: 70, align: 'center' },
    { key: 'title', label: '업무명', width: 220 },
    { key: 'assignee', label: '담당자', width: 120, align: 'center' },
    { key: 'priority', label: '우선순위', width: 100, align: 'center' },
    { key: 'dueDate', label: '마감일', width: 120, align: 'center' },
  ]);

  const [data] = useState<AXDGDataItem<TaskItem>[]>([
    { values: { id: 1, title: '결제 모듈 v2 마이그레이션', assignee: '김민수', priority: '높음', dueDate: '2026-08-25' } },
    { values: { id: 2, title: '모바일 뷰 반응형 UI 개선', assignee: '이수진', priority: '보통', dueDate: '2026-08-28' } },
    { values: { id: 3, title: '분기별 보안 감사 보고서 작성', assignee: '박도현', priority: '긴급', dueDate: '2026-08-20' } },
  ]);

  return (
    <div>
      <div style={{ marginBottom: 10, fontSize: 13, color: '#475569' }}>
        💡 컬럼 헤더 라벨을 마우스로 잡고 좌우로 드래그하여 순서를 바꿔보세요.
      </div>

      <AXDataGrid<TaskItem>
        width={700}
        height={240}
        columns={columns}
        data={data}
        rowKey="id"
        columnSortable={true} // 헤더 드래그 순서 변경 활성화
        onChangeColumns={(_, { columns: nextColumns }) => {
          console.log('변경된 컬럼 순서:', nextColumns.map(c => c.key));
          setColumns(nextColumns);
        }}
      />
    </div>
  );
}