컬럼 필터링과 정렬
문자·숫자·날짜·boolean 필터를 제어하고, 정렬 후에도 필터와 원본 데이터를 유지하는 예제.
확인할 동작
섹션 제목: “확인할 동작”각 조건 버튼은 필터 모델 전체를 해당 프리셋으로 바꿉니다. 정렬 버튼은 필터를 유지합니다. 헤더의 필터 버튼에서는 조건을 직접 편집할 수 있습니다.
| 조작 | 표시되는 행 |
|---|---|
| 초기 상태 | Alpha, Beta, Gamma, Delta |
| 이름 g로 시작 | Gamma — 기본 text 비교는 대소문자를 구분하지 않습니다. |
| 금액 100–200 | Alpha(120), Delta(160) — 양 끝 값을 포함합니다. |
| 2월 가입 | Beta, Gamma — UTC 날짜 기준입니다. |
| 활성 AND 금액 > 150 | Gamma, Delta — 다른 컬럼의 조건을 AND로 결합합니다. |
| 금액 100–200 → 금액 내림차순 | Delta, Alpha — 필터 모델과 원본 행 배열은 유지합니다. |
| 초기화 | 모든 필터와 정렬을 해제합니다. |
상태를 연결하는 방법
섹션 제목: “상태를 연결하는 방법”columns[].filter.kind로 비교할 값의 종류를 지정합니다. 이 예제는 안정적인 column ID와 row ID를 명시합니다.columnFiltering.model과openColumnId를 React 상태로 소유합니다.onChangeModel은 전체 다음 모델을,onChangeOpenColumnId는 다음 열린 컬럼 ID 또는 null을 전달합니다. 두 콜백을 각각 상태 setter에 연결합니다.- 원본
data를 그대로 전달합니다. 패키지가 표시할 행을 선택하고 그 결과에 정렬을 적용합니다.
필터는 controlled 구성입니다. 이 버전의 CominsTable에 sortModel prop을 추가하지 않습니다. 정렬 버튼은 ref.setSortModel()을 호출하고 onChangeSortModel은 변경 관찰용으로 사용합니다. 예제 아래의 정렬 JSON은 알림으로 받은 값입니다.
직접 조건 편집
섹션 제목: “직접 조건 편집”이름 헤더의 필터 아이콘을 열어 Value에 zz를 입력하면 빈 결과를 확인할 수 있습니다. Escape는 열린 편집기를 닫습니다. 필터를 초기화하면 원본 4행이 다시 나타납니다. 편집기 내부 라벨은 설치된 배포본의 영문 라벨을 사용합니다.
입력과 지원 경계
섹션 제목: “입력과 지원 경계”between의 number 값은 숫자로, 날짜는 정확한YYYY-MM-DD문자열로 전달합니다. datetime 문자열의 현지 날짜를 그대로 비교하는 예제가 아닙니다.- 원본 배열은 바뀌지 않습니다. 필터링과 정렬은 표시 순서에 적용합니다.
- 이 예제는 CSR flat data용입니다. Tree, 원격 Viewport, Infinite Scroll, Lazy Load와 결합하지 않습니다.
columnFiltering이 있으면 Row Drag는 사용할 수 없습니다. - Row Grouping, Summary Row와의 결합, 모든 operator 조합은 별도 예제 수록 대상입니다.
관련 문서
섹션 제목: “관련 문서”컬럼 필터링 가이드 · 정렬 ref 메서드 · 선택과 정렬 상태
직접 실행
섹션 제목: “직접 실행”import { useMemo, useRef, useState } from 'react';import { CominsTable, type CominsTableColumn, type CominsTableRef, type CominsColumnFilterModel, type CominsSortModel } from 'comins-table';import 'comins-table/styles.css';
type Row = { id: string; name: string; amount: number; joinedAt: string; active: boolean };const rows: readonly Row[] = [ { id: 'alpha', name: 'Alpha', amount: 120, joinedAt: '2026-01-10', active: true }, { id: 'beta', name: 'Beta', amount: 80, joinedAt: '2026-02-12', active: false }, { id: 'gamma', name: 'Gamma', amount: 240, joinedAt: '2026-02-20', active: true }, { id: 'delta', name: 'Delta', amount: 160, joinedAt: '2026-03-05', active: true },];
export default function TableFilteringDemo({ lang = 'ko' }: { lang?: string }) { const ko = lang === 'ko'; const table = useRef<CominsTableRef<Row>>(null); const [model, setModel] = useState<CominsColumnFilterModel>([]); const [openColumnId, setOpenColumnId] = useState<string | null>(null); const [observedSort, setObservedSort] = useState<CominsSortModel>([]); const columns = useMemo<CominsTableColumn<Row>[]>(() => [ { id: 'name', field: 'name', label: ko ? '이름' : 'Name', width: 150, filter: { kind: 'text' }, sort: true }, { id: 'amount', field: 'amount', label: ko ? '금액' : 'Amount', width: 130, filter: { kind: 'number' }, sort: true }, { id: 'joinedAt', field: 'joinedAt', label: ko ? '가입일' : 'Joined', width: 150, filter: { kind: 'date' }, sort: true }, { id: 'active', field: 'active', label: ko ? '활성' : 'Active', width: 110, filter: { kind: 'boolean' } }, ], [ko]); const preset = (next: CominsColumnFilterModel) => { setModel(next); setOpenColumnId(null); };
return <div className="demo" data-demo="table-filtering"> <div className="demo-toolbar"> <button onClick={() => preset([{ columnId: 'name', operator: 'startsWith', value: 'g' }])}>{ko ? '이름 g로 시작' : 'Name starts with g'}</button> <button onClick={() => preset([{ columnId: 'amount', operator: 'between', value: 100, valueTo: 200 }])}>{ko ? '금액 100–200' : 'Amount 100–200'}</button> <button onClick={() => preset([{ columnId: 'joinedAt', operator: 'between', value: '2026-02-01', valueTo: '2026-02-28' }])}>{ko ? '2월 가입' : 'Joined in February'}</button> <button onClick={() => preset([{ columnId: 'active', operator: 'equals', value: true }, { columnId: 'amount', operator: 'greaterThan', value: 150 }])}>{ko ? '활성 AND 금액 > 150' : 'Active AND amount > 150'}</button> <button onClick={() => table.current?.setSortModel([{ columnId: 'amount', direction: 'desc' }])}>{ko ? '금액 내림차순' : 'Amount descending'}</button> <button onClick={() => { preset([]); table.current?.clearSort(); }}>{ko ? '초기화' : 'Reset'}</button> </div> <div className="table-viewport"> <CominsTable ref={table} columns={columns} data={rows} getRowId={row => row.id} columnFiltering={{ model, openColumnId, onChangeModel: setModel, onChangeOpenColumnId: setOpenColumnId }} onChangeSortModel={setObservedSort} persistHeaderWhenEmpty emptyContent={{ render: () => <span>{ko ? '조건에 맞는 행이 없습니다.' : 'No matching rows.'}</span> }} /> </div> <p className="demo-status">{ko ? '원본 데이터는 항상 4행입니다. 헤더의 필터 버튼으로 조건을 직접 편집할 수 있습니다.' : 'Source data always contains 4 rows. Edit conditions with the header filter buttons.'}</p> <details open><summary>{ko ? '소유한 필터 모델' : 'Owned filter model'}</summary><pre data-filter-model>{JSON.stringify(model, null, 2)}</pre></details> <details><summary>{ko ? '정렬 변경 알림' : 'Observed sort changes'}</summary><pre data-sort-model>{JSON.stringify(observedSort, null, 2)}</pre></details> <details><summary>{ko ? '원본 행 순서' : 'Source row order'}</summary><pre data-source-ids>{rows.map(row => row.id).join(', ')}</pre></details> </div>;}