Analytics
DataTable
Dark ModeDataTable, TableToolbar, TablePagination을 조합하는 합성 테이블 컴포넌트. useDataTable hook의 tableProps를 스프레드하면 열 DnD, 헤더 팝오버(컬럼 설정), 행 선택(체크박스), 조건부 셀 스타일이 자동 연동됩니다.
import { DataTable } from "@/shared/ui/widget/analytics/table"합성 컴포넌트 구조
DataTable은 단독으로 사용하지 않고, 하위 컴포넌트를 직접 조합하여 구성합니다. useDataTable hook이 모든 상태를 통합 관리하며, 각 컴포넌트에 toolbarProps, tableProps, paginationProps를 스프레드하면 자동으로 연동됩니다.
| 요소 | 역할 |
|---|---|
useDataTable | 검색 · 필터 · 정렬 · 컬럼 가시성 · 페이지네이션 · 그룹화 · config 직렬화를 통합 관리하는 hook |
useJoinedDataTable | 마스터 키 기준으로 여러 DataSource를 가로 병합하는 hook. 내부적으로 useDataTable을 확장 |
TableToolbar | 필터 · 검색 · 다중 정렬 · 필드 가시성 + 커스텀 액션 슬롯 |
DataTable | 컬럼 헤더 렌더링 (클릭 → 컬럼명/정렬/너비 설정 팝오버) |
TableRow / TableCell | 행과 셀. Cell의 variant로 date · status · tag 프리셋 지원 |
GroupHeaderRow | 그룹 토글 헤더. 가로 스크롤 시 라벨 고정, 접기/펴기 지원 |
TablePagination | 페이지 이동 · 페이지 크기 선택 (그룹화 시 자동 비활성화) |
// 1. Hook으로 상태 생성
const table = useDataTable({
data, columns,
pageSize: 10,
editable: true, // 편집 모드 활성화
});
// 2. 각 영역에 props 스프레드 — 모든 기능 자동 연동
<TableToolbar
{...table.toolbarProps}
onAddRow={handleAddRow} // 행 추가 버튼
onAddColumn={handleAddColumn} // 열 추가 버튼
onDeleteSelected={table.removeSelectedRows} // 선택 삭제
onClearSelection={table.clearSelection}
/>
<DataTable {...table.tableProps}> {/* DnD + 체크박스 자동 */}
{table.rows.map(row => (
<TableRow
key={row.id}
selectable // 체크박스
selected={table.selectedIndices.has(table.getOriginalIndex(row))}
onSelectChange={() => table.toggleSelect(table.getOriginalIndex(row))}
>
<TableCell editable onCellChange={...}>{row.name}</TableCell>
</TableRow>
))}
</DataTable>
<TablePagination {...table.paginationProps} />포트폴리오 테이블
Toolbar의 필터 · 검색 · 다중 정렬 · 필드 가시성과 헤더 팝오버, 페이지네이션이 모두 연동되는 전체 조합 예시입니다.
| 포트폴리오 회사 | 현재 가치 | 회수액 | MOIC | IRR | 등급 | 섹터 | Date | |
|---|---|---|---|---|---|---|---|---|
| (주)테크스타트 | 12억 | 3억 | 2.4x | 35% | In Progress | 딥테크 | 2018 | |
| (주)헬스랩 | 4.5억 | 1억 | 1.5x | 18% | Done | 바이오 | 2021 | |
| (주)그린에너지 | 8억 | 0 | 0.8x | -5% | Pending | 클린테크 | 2023 | |
| (주)핀테크솔루션 | 15억 | 5억 | 2.9x | 42% | Cancelled | 핀테크 | 2020 | |
| (주)푸드커넥트 | 2.5억 | 0.5억 | 1.3x | 12% | In Review | F&B | 2022 | |
| (주)에듀플러스 | 6억 | 2억 | 2.0x | 28% | Done | 에듀테크 | 2021 | |
| (주)로보틱스AI | 20억 | 0 | 2.5x | 38% | In Progress | 딥테크 | 2023 | |
| (주)클라우드넷 | 3억 | 1억 | 0.7x | -8% | Cancelled | SaaS | 2019 |
TableViewConfig — 테이블 상태 직렬화
table.config는 현재 테이블의 필터 · 정렬 · 컬럼 너비 · 가시성 등을 JSON으로 직렬화한 객체입니다. DB에 저장했다가 applyConfig()로 복원하면 어디서 열어도 동일한 테이블 뷰를 재현할 수 있습니다.
// table.config — 현재 테이블 상태를 JSON으로 직렬화
const { config, applyConfig } = useDataTable({ ... });
// config 값 예시 (version 2)
{
"version": 2,
"filters": [{ "columnKey": "sector", "comparison": "contains", "value": "딥테크" }],
"sortConfigs": [{ "key": "irr", "direction": "desc" }],
"visibleKeys": ["company", "irr", "grade"],
"columnOrder": ["company", "irr", "grade", "sector"],
"columns": [{ "key": "company", "width": 200, "variant": "text" }],
"searchKey": "company",
"searchValue": "",
"pageSize": 10,
"page": 1,
"collapsedGroups": []
}
// DB에서 불러온 설정 복원
applyConfig(savedConfig);table.config — 현재 포트폴리오 테이블 설정 (실시간)
{
"version": 2,
"filters": [],
"sortConfigs": [],
"visibleKeys": [
"company",
"currentValue",
"recovery",
"moic",
"irr",
"grade",
"sector",
"date"
],
"columnOrder": [
"company",
"currentValue",
"recovery",
"moic",
"irr",
"grade",
"sector",
"date"
],
"columns": [
{
"key": "company",
"label": "포트폴리오 회사",
"width": 180
},
{
"key": "currentValue",
"label": "현재 가치",
"width": 90
},
{
"key": "recovery",
"label": "회수액",
"width": 80
},
{
"key": "moic",
"label": "MOIC",
"width": 80
},
{
"key": "irr",
"label": "IRR",
"width": 80
},
{
"key": "grade",
"label": "등급",
"width": 110
},
{
"key": "sector",
"label": "섹터",
"width": 120
},
{
"key": "date",
"label": "Date",
"width": 100
}
],
"searchKey": "company",
"searchValue": "",
"pageSize": 10,
"page": 1,
"collapsedGroups": []
}데이터 소스 병합 (Horizontal JOIN)
useJoinedDataTable hook을 사용하면 마스터 키를 기준으로 여러 데이터 소스의 컬럼을 가로로 병합할 수 있습니다. 툴바의 필드 드롭다운에서 소스별로 그룹화된 열을 개별적으로 표시/숨김 할 수 있습니다. 마스터 키 컬럼은 가로 스크롤 시에도 왼쪽에 고정됩니다.
// 데이터 소스 정의
const sources: DataSource[] = [
{
id: "investment",
label: "투자",
color: "primary",
columns: [
{ key: "value", label: "현재 가치", sortable: true },
{ key: "irr", label: "IRR", sortable: true },
],
data: investmentData,
},
// ...다른 소스
];
// Hook 생성
const joined = useJoinedDataTable({
masterKey: "companyName",
masterColumn: { key: "companyName", label: "기업명", primary: true },
sources,
});
// 툴바 — 필드 드롭다운에서 소스별 열 관리
<TableToolbar {...joined.toolbarProps} />
// 테이블 렌더링 (masterKey 컬럼은 자동 고정)
<DataTable {...joined.tableProps}>
{joined.rows.map(row => (
<TableRow key={String(row.companyName)}>
{joined.visibleKeys.map(key => (
<TableCell key={key} primary={key === "companyName"}>
{String(row[key] ?? "")}
</TableCell>
))}
</TableRow>
))}
</DataTable>// groupBy로 행 그룹화 (그룹화 시 페이지네이션 자동 비활성화)
const joined = useJoinedDataTable({
masterKey: "companyName",
masterColumn: masterCol,
sources,
groupBy: {
key: "building",
label: "건물",
resolve: { "(주)테크스타트": "A동", "(주)헬스랩": "B동", ... },
},
});
// groups가 있으면 그룹 렌더링, 없으면 flat 렌더링
<DataTable {...joined.tableProps}>
{joined.groups?.map(group => (
<Fragment key={group.key}>
<GroupHeaderRow
label={`${joined.groupByLabel}: ${group.key}`}
count={group.count}
collapsed={group.collapsed}
onToggle={() => joined.toggleGroup(group.key)}
/>
{!group.collapsed && group.rows.map(row => (
<TableRow key={...}>
{/* cells */}
</TableRow>
))}
</Fragment>
))}
</DataTable>| 투자 | 멘토링 | PR/보도 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 기업명 | 현재 가치 | 회수액 | MOIC | IRR | 멘토명 | 기간 | 멘토링 상태 | 보도일 | 매체 | 헤드라인 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
건물: A동(4건) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (주)테크스타트 | 12억 | 3억 | 2.4x | 35% | 김혁신 | 6개월 | 완료 | 2024-01-15 | 한국경제 | AI 스타트업 성장 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (주)그린에너지 | 8억 | 0 | 0.8x | -5% | 박전문 | 4개월 | 완료 | 2024-03-10 | 조선비즈 | 탄소중립 기술 특허 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (주)푸드커넥트 | 2.5억 | 0.5억 | 1.3x | 12% | 정식품 | 2개월 | 대기 | 2024-04-01 | 중앙일보 | F&B 플랫폼 론칭 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (주)로보틱스AI | 20억 | 0 | 2.5x | 38% | 김혁신 | 6개월 | 진행중 | 2024-05-12 | 전자신문 | 산업용 로봇 출시 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
건물: B동(4건) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (주)헬스랩 | 4.5억 | 1억 | 1.5x | 18% | 이성장 | 3개월 | 진행중 | 2024-02-20 | 매일경제 | 디지털 헬스케어 확장 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (주)핀테크솔루션 | 15억 | 5억 | 2.9x | 42% | 최금융 | 6개월 | 완료 | 2023-11-05 | 서울경제 | 시리즈B 투자 유치 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (주)에듀플러스 | 6억 | 2억 | 2.0x | 28% | 한교육 | 5개월 | 진행중 | 2024-01-30 | 동아일보 | 에듀테크 수상 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (주)클라우드넷 | 3억 | 1억 | 0.7x | -8% | 윤클라 | 3개월 | 중단 | 2023-09-22 | 디지털타임스 | 클라우드 보안 강화 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
고급 기능 데모
열 순서 변경, 뷰 개인화(localStorage), 동적 열/행 추가 기능을 체험해 보세요. 툴바의 순서 버튼으로 열 순서를 변경하고, 북마크 아이콘으로 현재 설정을 저장/불러올 수 있습니다.
| 기업명 | 업종 | 매출액 | 직원 수 | ||
|---|---|---|---|---|---|
| 알파테크 | AI | 120억 | 45 | ||
| 그린에너지 | 에너지 | 85억 | 32 | ||
| 블루헬스 | 헬스케어 | 200억 | 78 | ||
| 데이터플로우 | SaaS | 50억 | 20 | ||
| 스마트팜 | 농업 | 30억 | 15 |
table.config — 실시간 설정
{
"version": 2,
"filters": [],
"sortConfigs": [],
"visibleKeys": [
"name",
"industry",
"revenue",
"employees"
],
"columnOrder": [
"name",
"industry",
"revenue",
"employees"
],
"columns": [
{
"key": "name",
"label": "기업명",
"width": 140
},
{
"key": "industry",
"label": "업종",
"width": 100
},
{
"key": "revenue",
"label": "매출액",
"width": 100,
"align": "right"
},
{
"key": "employees",
"label": "직원 수",
"width": 90,
"align": "right"
}
],
"searchKey": null,
"searchValue": "",
"pageSize": 10,
"page": 1,
"collapsedGroups": []
}TableCell Variants
| Variant | 미리보기 | 설명 |
|---|---|---|
text (기본) | 일반 텍스트 내용 | variant 생략 시 기본값 |
date | 2024-03-15 | 캘린더 아이콘 + 날짜 텍스트 |
status | DoneIn ProgressPending | 컬러 dot + 상태 텍스트 |
tag | 딥테크 | pill 배경의 태그 형태 |
Props
DataTable
| Prop | Type | Required | Default | 설명 |
|---|---|---|---|---|
columns | TableColumn[] | ✓ | — | 컬럼 정의 목록. |
children | ReactNode | ✓ | — | 테이블 바디 영역. TableRow + TableCell 조합. |
sortConfigs | SortConfig[] | — | [] | 다중 정렬 설정. |
onColumnUpdate | (key: string, updates: ColumnUpdate) => void | — | — | 헤더 팝오버에서 컬럼 속성 변경 시 호출. |
onColumnOrderChange | (keys: string[]) => void | — | — | 열 DnD 시 호출. tableProps에 자동 포함. |
sourceGroups | SourceGroup[] | — | — | 데이터 소스 그룹. 그룹 헤더 + 그룹 DnD 활성화. |
selectable | boolean | — | — | 행 선택 체크박스 표시. tableProps에 자동 포함 (editable=true 시). |
allSelected | boolean | — | — | 전체 선택 상태. tableProps에 자동 포함. |
someSelected | boolean | — | — | 부분 선택 상태 (indeterminate). tableProps에 자동 포함. |
onToggleSelectAll | () => void | — | — | 전체 선택 토글. tableProps에 자동 포함. |
className | string | — | — | 루트 요소 추가 클래스. |
TableRow
| Prop | Type | Required | Default | 설명 |
|---|---|---|---|---|
children | ReactNode | ✓ | — | TableCell 목록. |
selected | boolean | — | — | 행 선택 하이라이트. |
selectable | boolean | — | — | 체크박스 표시 여부. |
onSelectChange | () => void | — | — | 체크박스 토글 콜백. |
onClick | () => void | — | — | 행 클릭 핸들러. |
className | string | — | — | 추가 클래스. |
TableCell
| Prop | Type | Required | Default | 설명 |
|---|---|---|---|---|
children | ReactNode | — | — | 셀 콘텐츠. |
variant | CellVariant | — | 'text' | 셀 유형. |
statusColor | StatusColor | — | 'grey' | 상태/태그 계열 색상. |
conditions | ColumnCondition[] | — | — | 조건부 스타일. 셀 텍스트 매칭 시 색상/variant 오버라이드. |
align | 'left' | 'center' | 'right' | — | 'left' | 텍스트 정렬. |
primary | boolean | — | — | 좌측 sticky 고정 컬럼. |
editable | boolean | — | — | 더블클릭 인라인 편집 활성화. |
onCellChange | (value: string) => void | — | — | 편집 확정 시 콜백. |
className | string | — | — | 추가 클래스. |