From 81aa92fecc298d66eb420468316bcf7a7213171c Mon Sep 17 00:00:00 2001
From: joonhoekim <26rote@gmail.com>
Date: Sun, 30 Nov 2025 17:12:17 +0900
Subject: (김준회) group 기능 추가, 기타 버그 수정, preset 기능추가, 테스트
 페이지 추가
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../client-table/client-table-column-header.tsx    | 70 +++++++++++++++++++---
 1 file changed, 63 insertions(+), 7 deletions(-)

(limited to 'components/client-table/client-table-column-header.tsx')

diff --git a/components/client-table/client-table-column-header.tsx b/components/client-table/client-table-column-header.tsx
index 12dc57ac..2d8e5bce 100644
--- a/components/client-table/client-table-column-header.tsx
+++ b/components/client-table/client-table-column-header.tsx
@@ -1,7 +1,7 @@
 "use client"
 
 import * as React from "react"
-import { Header } from "@tanstack/react-table"
+import { Header, Column } from "@tanstack/react-table"
 import { useSortable } from "@dnd-kit/sortable"
 import { CSS } from "@dnd-kit/utilities"
 import { flexRender } from "@tanstack/react-table"
@@ -20,19 +20,29 @@ import {
   PinOff,
   MoveLeft,
   MoveRight,
+  Group,
+  Ungroup,
 } from "lucide-react"
 import { cn } from "@/lib/utils"
-import { ClientTableFilter } from "./client-table-filter"
+import { ClientTableFilter } from "../client-table/client-table-filter"
 
 interface ClientTableColumnHeaderProps<TData, TValue>
   extends React.HTMLAttributes<HTMLTableHeaderCellElement> {
   header: Header<TData, TValue>
   enableReordering?: boolean
+  renderHeaderVisualFeedback?: (props: {
+    column: Column<TData, TValue>
+    isPinned: boolean | string
+    isSorted: boolean | string
+    isFiltered: boolean
+    isGrouped: boolean
+  }) => React.ReactNode
 }
 
 export function ClientTableColumnHeader<TData, TValue>({
   header,
   enableReordering = true,
+  renderHeaderVisualFeedback,
   className,
   ...props
 }: ClientTableColumnHeaderProps<TData, TValue>) {
@@ -46,7 +56,7 @@ export function ClientTableColumnHeader<TData, TValue>({
     isDragging,
   } = useSortable({
     id: header.id,
-    disabled: !enableReordering,
+    disabled: !enableReordering || column.getIsResizing(),
   })
 
   // -- Styles --
@@ -62,14 +72,18 @@ export function ClientTableColumnHeader<TData, TValue>({
 
   // Pinning Styles
   const isPinned = column.getIsPinned()
+  const isSorted = column.getIsSorted()
+  const isFiltered = column.getFilterValue() !== undefined
+  const isGrouped = column.getIsGrouped()
+
   if (isPinned === "left") {
     style.left = `${column.getStart("left")}px`
     style.position = "sticky"
-    style.zIndex = 20
+    style.zIndex = 30 // Pinned columns needs to be higher than normal headers
   } else if (isPinned === "right") {
     style.right = `${column.getAfter("right")}px`
     style.position = "sticky"
-    style.zIndex = 20
+    style.zIndex = 30 // Pinned columns needs to be higher than normal headers
   }
 
   // -- Handlers --
@@ -77,6 +91,7 @@ export function ClientTableColumnHeader<TData, TValue>({
   const handlePinLeft = () => column.pin("left")
   const handlePinRight = () => column.pin("right")
   const handleUnpin = () => column.pin(false)
+  const handleToggleGrouping = () => column.toggleGrouping()
 
   // -- Content --
   const content = (
@@ -100,12 +115,14 @@ export function ClientTableColumnHeader<TData, TValue>({
             )}
           </span>
         )}
+        {isGrouped && <Group className="h-4 w-4 text-blue-500" />}
       </div>
 
       {/* Resize Handle */}
       <div
         onMouseDown={header.getResizeHandler()}
         onTouchStart={header.getResizeHandler()}
+        onPointerDown={(e) => e.stopPropagation()}
         onClick={(e) => e.stopPropagation()} // Prevent sort trigger
         className={cn(
           "absolute right-0 top-0 h-full w-2 cursor-col-resize select-none touch-none z-10",
@@ -117,6 +134,25 @@ export function ClientTableColumnHeader<TData, TValue>({
 
       {/* Filter */}
       {column.getCanFilter() && <ClientTableFilter column={column} />}
+
+      {/* Visual Feedback Indicators */}
+      {renderHeaderVisualFeedback ? (
+        renderHeaderVisualFeedback({
+          column,
+          isPinned,
+          isSorted,
+          isFiltered,
+          isGrouped,
+        })
+      ) : (
+        (isPinned || isFiltered || isGrouped) && (
+          <div className="absolute top-0.5 right-1 flex gap-1 z-10 pointer-events-none">
+            {isPinned && <div className="h-1.5 w-1.5 rounded-full bg-blue-500" />}
+            {isFiltered && <div className="h-1.5 w-1.5 rounded-full bg-yellow-500" />}
+            {isGrouped && <div className="h-1.5 w-1.5 rounded-full bg-green-500" />}
+          </div>
+        )
+      )}
     </>
   )
 
@@ -141,9 +177,9 @@ export function ClientTableColumnHeader<TData, TValue>({
           colSpan={header.colSpan}
           style={style}
           className={cn(
-            "border-b px-4 py-2 text-left text-sm font-medium bg-muted group",
+            "border-b px-4 py-2 text-left text-sm font-medium bg-muted group transition-colors",
             isDragging ? "opacity-50 bg-accent" : "",
-            isPinned ? "bg-muted shadow-[0_0_10px_rgba(0,0,0,0.1)]" : "",
+            isPinned ? "shadow-[0_0_10px_rgba(0,0,0,0.1)]" : "",
             className
           )}
           {...attributes}
@@ -158,6 +194,26 @@ export function ClientTableColumnHeader<TData, TValue>({
           <EyeOff className="mr-2 h-4 w-4" />
           Hide Column
         </ContextMenuItem>
+
+        {column.getCanGroup() && (
+          <>
+            <ContextMenuSeparator />
+            <ContextMenuItem onClick={handleToggleGrouping}>
+              {isGrouped ? (
+                <>
+                  <Ungroup className="mr-2 h-4 w-4" />
+                  Ungroup
+                </>
+              ) : (
+                <>
+                  <Group className="mr-2 h-4 w-4" />
+                  Group by {column.id}
+                </>
+              )}
+            </ContextMenuItem>
+          </>
+        )}
+
         <ContextMenuSeparator />
         <ContextMenuItem onClick={handlePinLeft}>
           <MoveLeft className="mr-2 h-4 w-4" />
-- 
cgit v1.2.3

