summaryrefslogtreecommitdiff
path: root/components/client-data-table
diff options
context:
space:
mode:
Diffstat (limited to 'components/client-data-table')
-rw-r--r--components/client-data-table/data-table.tsx300
1 files changed, 154 insertions, 146 deletions
diff --git a/components/client-data-table/data-table.tsx b/components/client-data-table/data-table.tsx
index 3e009302..371a1dab 100644
--- a/components/client-data-table/data-table.tsx
+++ b/components/client-data-table/data-table.tsx
@@ -49,8 +49,9 @@ interface DataTableProps<TData, TValue> {
children?: React.ReactNode
/** 선택 상태 초기화 트리거 */
clearSelection?: boolean
- initialColumnPinning?: ColumnPinningState // 추가
-
+ initialColumnPinning?: ColumnPinningState
+ /** Table 인스턴스를 상위 컴포넌트에 전달하는 콜백 */
+ onTableReady?: (table: Table<TData>) => void
}
export function ClientDataTable<TData, TValue>({
@@ -63,7 +64,8 @@ export function ClientDataTable<TData, TValue>({
maxHeight,
onSelectedRowsChange,
clearSelection,
- initialColumnPinning
+ initialColumnPinning,
+ onTableReady
}: DataTableProps<TData, TValue>) {
// (1) React Table 상태
@@ -118,6 +120,13 @@ export function ClientDataTable<TData, TValue>({
useAutoSizeColumns(table, autoSizeColumns)
+ // 🆕 Table 인스턴스를 상위 컴포넌트에 전달
+ React.useEffect(() => {
+ if (onTableReady) {
+ onTableReady(table)
+ }
+ }, [table, onTableReady])
+
React.useEffect(() => {
if (!onSelectedRowsChange) return
const selectedRows = table
@@ -164,6 +173,7 @@ export function ClientDataTable<TData, TValue>({
}),
}
}
+
// 🎯 테이블 총 너비 계산
const getTableWidth = React.useCallback(() => {
const totalSize = table.getCenterTotalSize() + table.getLeftTotalSize() + table.getRightTotalSize()
@@ -206,174 +216,172 @@ export function ClientDataTable<TData, TValue>({
{children}
</ClientDataTableAdvancedToolbar>
-
- <div
- className="max-w-[100vw] overflow-auto"
- style={{ maxHeight: maxHeight || '34rem' }}
- onScroll={handleScroll} // 🎯 스크롤 이벤트 핸들러 추가
- >
- <UiTable
+ <div
+ className="max-w-[100vw] overflow-auto"
+ style={{ maxHeight: maxHeight || '34rem' }}
+ onScroll={handleScroll} // 🎯 스크롤 이벤트 핸들러 추가
+ >
+ <UiTable
className={cn(
"[&>thead]:sticky [&>thead]:top-0 [&>thead]:z-10",
!hasNestedHeader && "table-fixed" // nested header가 없으면 table-fixed 적용
)}
style={{ minWidth: hasNestedHeader ? getTableWidth() : undefined }}>
- {/* nested header가 있으면 table-fixed 제거, 없으면 적용 */}
- <TableHeader>
- {table.getHeaderGroups().map((headerGroup) => (
- <TableRow key={headerGroup.id} className={compactStyles.headerRow}>
- {headerGroup.headers.map((header) => {
- // 만약 이 컬럼이 현재 "그룹핑" 상태라면 헤더도 표시하지 않음
- if (header.column.getIsGrouped()) {
- return null
- }
+ {/* nested header가 있으면 table-fixed 제거, 없으면 적용 */}
+ <TableHeader>
+ {table.getHeaderGroups().map((headerGroup) => (
+ <TableRow key={headerGroup.id} className={compactStyles.headerRow}>
+ {headerGroup.headers.map((header) => {
+ // 만약 이 컬럼이 현재 "그룹핑" 상태라면 헤더도 표시하지 않음
+ if (header.column.getIsGrouped()) {
+ return null
+ }
- return (
- <TableHead
- key={header.id}
- colSpan={header.colSpan}
- data-column-id={header.column.id}
- className={compactStyles.header}
- style={{
- ...getPinnedStyle(header.column, true), // 🎯 헤더임을 명시
- // 부모 그룹 헤더는 colSpan으로 너비가 결정되므로 width 설정하지 않음
- // 자식 헤더만 개별 width 설정
- ...(!('columns' in header.column.columnDef) && { width: header.getSize() }),
- }}
- >
- <div style={{ position: "relative" }}>
- {header.isPlaceholder
- ? null
- : flexRender(
- header.column.columnDef.header,
- header.getContext()
- )}
-
- {/* 부모 그룹 헤더는 리사이즈 불가, 자식 헤더만 리사이즈 가능 */}
- {header.column.getCanResize() && !('columns' in header.column.columnDef) && (
- <DataTableResizer header={header} />
+ return (
+ <TableHead
+ key={header.id}
+ colSpan={header.colSpan}
+ data-column-id={header.column.id}
+ className={compactStyles.header}
+ style={{
+ ...getPinnedStyle(header.column, true), // 🎯 헤더임을 명시
+ // 부모 그룹 헤더는 colSpan으로 너비가 결정되므로 width 설정하지 않음
+ // 자식 헤더만 개별 width 설정
+ ...(!('columns' in header.column.columnDef) && { width: header.getSize() }),
+ }}
+ >
+ <div style={{ position: "relative" }}>
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext()
)}
- </div>
- </TableHead>
- )
- })}
- </TableRow>
- ))}
- </TableHeader>
- <TableBody>
- {table.getRowModel().rows?.length ? (
- table.getRowModel().rows.map((row) => {
- // ---------------------------------------------------
- // 1) "그룹핑 헤더" Row인지 확인
- // ---------------------------------------------------
- if (row.getIsGrouped()) {
- // row.groupingColumnId로 어떤 컬럼을 기준으로 그룹화 되었는지 알 수 있음
- const groupingColumnId = row.groupingColumnId ?? ""
- const groupingColumn = table.getColumn(groupingColumnId) // 해당 column 객체
+
+ {/* 부모 그룹 헤더는 리사이즈 불가, 자식 헤더만 리사이즈 가능 */}
+ {header.column.getCanResize() && !('columns' in header.column.columnDef) && (
+ <DataTableResizer header={header} />
+ )}
+ </div>
+ </TableHead>
+ )
+ })}
+ </TableRow>
+ ))}
+ </TableHeader>
+ <TableBody>
+ {table.getRowModel().rows?.length ? (
+ table.getRowModel().rows.map((row) => {
+ // ---------------------------------------------------
+ // 1) "그룹핑 헤더" Row인지 확인
+ // ---------------------------------------------------
+ if (row.getIsGrouped()) {
+ // row.groupingColumnId로 어떤 컬럼을 기준으로 그룹화 되었는지 알 수 있음
+ const groupingColumnId = row.groupingColumnId ?? ""
+ const groupingColumn = table.getColumn(groupingColumnId) // 해당 column 객체
- // 컬럼 라벨 가져오기
- let columnLabel = groupingColumnId
- if (groupingColumn) {
- const headerDef = groupingColumn.columnDef.meta?.excelHeader
- if (typeof headerDef === "string") {
- columnLabel = headerDef
- }
+ // 컬럼 라벨 가져오기
+ let columnLabel = groupingColumnId
+ if (groupingColumn) {
+ const headerDef = groupingColumn.columnDef.meta?.excelHeader
+ if (typeof headerDef === "string") {
+ columnLabel = headerDef
}
-
- return (
- <TableRow
- key={row.id}
- className={compactStyles.groupRow}
- data-state={row.getIsExpanded() && "expanded"}
- >
- {/* 그룹 헤더는 한 줄에 합쳐서 보여주고, 토글 버튼 + 그룹 라벨 + 값 표기 */}
- <TableCell
- colSpan={table.getVisibleFlatColumns().length}
- className={compact ? "py-1 px-2" : ""}
- >
- {/* 확장/축소 버튼 (아이콘 중앙 정렬 + Indent) */}
- {row.getCanExpand() && (
- <button
- onClick={row.getToggleExpandedHandler()}
- className="inline-flex items-center justify-center mr-2 w-5 h-5"
- style={{
- // row.depth: 0이면 top-level, 1이면 그 하위 등
- marginLeft: `${row.depth * 1.5}rem`,
- }}
- >
- {row.getIsExpanded() ? (
- <ChevronUp size={compact ? 14 : 16} />
- ) : (
- <ChevronRight size={compact ? 14 : 16} />
- )}
- </button>
- )}
-
- {/* Group Label + 값 */}
- <span className="font-semibold">
- {columnLabel}: {row.getValue(groupingColumnId)}
- </span>
- <span className="ml-2 text-xs text-muted-foreground">
- ({row.subRows.length} rows)
- </span>
- </TableCell>
- </TableRow>
- )
}
- // ---------------------------------------------------
- // 2) 일반 Row
- // → "그룹핑된 컬럼"은 숨긴다
- // ---------------------------------------------------
return (
<TableRow
key={row.id}
- className={compactStyles.row}
- data-state={row.getIsSelected() && "selected"}
+ className={compactStyles.groupRow}
+ data-state={row.getIsExpanded() && "expanded"}
>
- {row.getVisibleCells().map((cell) => {
- // 이 셀의 컬럼이 grouped라면 숨긴다
- if (cell.column.getIsGrouped()) {
- return null
- }
-
- return (
- <TableCell
- key={cell.id}
- data-column-id={cell.column.id}
- className={compactStyles.cell}
+ {/* 그룹 헤더는 한 줄에 합쳐서 보여주고, 토글 버튼 + 그룹 라벨 + 값 표기 */}
+ <TableCell
+ colSpan={table.getVisibleFlatColumns().length}
+ className={compact ? "py-1 px-2" : ""}
+ >
+ {/* 확장/축소 버튼 (아이콘 중앙 정렬 + Indent) */}
+ {row.getCanExpand() && (
+ <button
+ onClick={row.getToggleExpandedHandler()}
+ className="inline-flex items-center justify-center mr-2 w-5 h-5"
style={{
- ...getPinnedStyle(cell.column, false), // 🎯 바디 셀임을 명시
- width: cell.column.getSize() // 🎯 width 별도 설정
+ // row.depth: 0이면 top-level, 1이면 그 하위 등
+ marginLeft: `${row.depth * 1.5}rem`,
}}
>
- {flexRender(
- cell.column.columnDef.cell,
- cell.getContext()
+ {row.getIsExpanded() ? (
+ <ChevronUp size={compact ? 14 : 16} />
+ ) : (
+ <ChevronRight size={compact ? 14 : 16} />
)}
- </TableCell>
- )
- })}
+ </button>
+ )}
+
+ {/* Group Label + 값 */}
+ <span className="font-semibold">
+ {columnLabel}: {row.getValue(groupingColumnId)}
+ </span>
+ <span className="ml-2 text-xs text-muted-foreground">
+ ({row.subRows.length} rows)
+ </span>
+ </TableCell>
</TableRow>
)
- })
- ) : (
+ }
+
// ---------------------------------------------------
- // 3) 데이터가 없을 때
+ // 2) 일반 Row
+ // → "그룹핑된 컬럼"은 숨긴다
// ---------------------------------------------------
- <TableRow>
- <TableCell
- colSpan={table.getAllColumns().length}
- className={compactStyles.emptyRow + " text-center"}
+ return (
+ <TableRow
+ key={row.id}
+ className={compactStyles.row}
+ data-state={row.getIsSelected() && "selected"}
>
- No results.
- </TableCell>
- </TableRow>
- )}
- </TableBody>
- </UiTable>
- </div>
+ {row.getVisibleCells().map((cell) => {
+ // 이 셀의 컬럼이 grouped라면 숨긴다
+ if (cell.column.getIsGrouped()) {
+ return null
+ }
+ return (
+ <TableCell
+ key={cell.id}
+ data-column-id={cell.column.id}
+ className={compactStyles.cell}
+ style={{
+ ...getPinnedStyle(cell.column, false), // 🎯 바디 셀임을 명시
+ width: cell.column.getSize() // 🎯 width 별도 설정
+ }}
+ >
+ {flexRender(
+ cell.column.columnDef.cell,
+ cell.getContext()
+ )}
+ </TableCell>
+ )
+ })}
+ </TableRow>
+ )
+ })
+ ) : (
+ // ---------------------------------------------------
+ // 3) 데이터가 없을 때
+ // ---------------------------------------------------
+ <TableRow>
+ <TableCell
+ colSpan={table.getAllColumns().length}
+ className={compactStyles.emptyRow + " text-center"}
+ >
+ No results.
+ </TableCell>
+ </TableRow>
+ )}
+ </TableBody>
+ </UiTable>
+ </div>
<ClientDataTablePagination table={table} />
</div>