From 1a2241c40e10193c5ff7008a7b7b36cc1d855d96 Mon Sep 17 00:00:00 2001 From: joonhoekim <26rote@gmail.com> Date: Tue, 25 Mar 2025 15:55:45 +0900 Subject: initial commit --- components/data-table/data-table-group-list.tsx | 317 ++++++++++++++++++++++++ 1 file changed, 317 insertions(+) create mode 100644 components/data-table/data-table-group-list.tsx (limited to 'components/data-table/data-table-group-list.tsx') diff --git a/components/data-table/data-table-group-list.tsx b/components/data-table/data-table-group-list.tsx new file mode 100644 index 00000000..cde1cadd --- /dev/null +++ b/components/data-table/data-table-group-list.tsx @@ -0,0 +1,317 @@ +"use client" + +import * as React from "react" +import { type Table } from "@tanstack/react-table" +import { useQueryState, parseAsArrayOf, parseAsString } from "nuqs" +import { Layers, Check, ChevronsUpDown, GripVertical, XCircle } from "lucide-react" + +import { toSentenceCase, cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { Badge } from "@/components/ui/badge" +import { + Popover, + PopoverTrigger, + PopoverContent, +} from "@/components/ui/popover" +import { + Command, + CommandList, + CommandGroup, + CommandItem, + CommandInput, + CommandEmpty, +} from "@/components/ui/command" +import { + Sortable, + SortableItem, + SortableDragHandle, +} from "@/components/ui/sortable" + +interface DataTableGroupListProps { + /** TanStack Table 인스턴스 (grouping을 이미 사용할 수 있어야 함) */ + table: Table + /** 정렬과 동일하게 URL 쿼리에 grouping을 저장할 때 쓰는 debounce 시간 (ms) */ + debounceMs: number + /** shallow 라우팅 여부 */ + shallow?: boolean +} + +export function DataTableGroupList({ + table, + debounceMs, + shallow, +}: DataTableGroupListProps) { + const id = React.useId() + + // ------------------------------------------------------ + // 1) 초기 그룹핑 상태 + URL Query State 동기화 + // ------------------------------------------------------ + const initialGrouping = (table.initialState.grouping ?? []) as string[] + + // group 쿼리 파라미터를 string[]로 파싱 + // parseAsArrayOf(parseAsString, ',')를 이용 + const [grouping, setGrouping] = useQueryState( + "group", + parseAsArrayOf(parseAsString, ",") + .withDefault(initialGrouping) + .withOptions({ + clearOnDefault: true, + shallow, + }) + ) + + // TanStack Table의 `table.setGrouping()`과 동기화 + // (정렬 모달 예시에서 setSorting()을 쓰듯이 여기서는 setGrouping() 호출) + React.useEffect(() => { + table.setGrouping(grouping) + }, [grouping, table]) + + // 이미 중복 추가된 그룹은 제거 + // (정렬 예시에서도 uniqueSorting 했듯이) + const uniqueGrouping = React.useMemo( + () => grouping.filter((id, i, self) => self.indexOf(id) === i), + [grouping] + ) + + // ------------------------------------------------------ + // 2) 그룹핑 가능한 컬럼만 골라내기 + // ------------------------------------------------------ + const groupableColumns = React.useMemo( + () => + table + .getAllColumns() + .filter((col) => col.getCanGroup?.() !== false) + .map((col) => ({ + id: col.id, + label: toSentenceCase(col.id), + })), + [table] + ) + + // 이미 그룹핑 중인 컬럼 제외하고 "추가 가능"한 컬럼들 + const ungroupedColumns = React.useMemo(() => { + return groupableColumns.filter( + (column) => !grouping.includes(column.id) + ) + }, [groupableColumns, grouping]) + + + + // ------------------------------------------------------ + // 3) 그룹 배열을 업데이트하는 함수들 + // ------------------------------------------------------ + + // 드래그/드롭으로 순서 변경 + function onGroupOrderChange(newGroups: string[]) { + setGrouping(newGroups) + } + + // "Add group" : 아직 그룹핑되지 않은 첫 번째 컬럼 추가 + function addGroup() { + const firstAvailable = ungroupedColumns[0] + if (!firstAvailable) return + setGrouping([...grouping, firstAvailable.id]) + } + + // 특정 아이템(그룹 컬럼 id) 제거 + function removeGroup(id: string) { + setGrouping((prev) => prev.filter((g) => g !== id)) + } + + // 전체 그룹핑 초기화 + function resetGrouping() { + setGrouping([]) + } + + // ------------------------------------------------------ + // 4) 렌더링 + // ------------------------------------------------------ + + return ( + ({ id }))} + onValueChange={(items) => { + // 드래그 완료 시 string[] 형태로 되돌림 + onGroupOrderChange(items.map((i) => i.id)) + }} + // overlay : 드래그 중 placeholder UI + overlay={ +
+
+
+
+
+ } + > + + + + + + 0 ? "gap-3.5" : "gap-2" + )} + > + {uniqueGrouping.length > 0 ? ( + <> +

Group by

+

+ 그룹핑은 불러온 데이터에 한해서 그룹핑이 됩니다. +

+ + + ) : ( +
+

No grouping applied

+

+ Add grouping to organize your results. +

+
+ )} + + {/* 그룹 목록 */} +
+
+ {uniqueGrouping.map((colId) => { + // SortableItem에 key로 colId + return ( + +
+ + + + + + + + + No columns found. + + {ungroupedColumns.map((column) => ( + { + // colId -> 새로 선택한 value로 교체 + setGrouping((prev) => + prev.map((g) => + g === colId ? value : g + ) + ) + }} + > + + {column.label} + + + ))} + + + + + + + {/* remove group */} + + + {/* drag handle */} + + +
+
+ ) + })} +
+
+ +
+ {/* 새 그룹 추가 */} + + {grouping.length > 0 && ( + + )} +
+
+
+ + ) +} \ No newline at end of file -- cgit v1.2.3