From bac0228d21b7195065e9cddcc327ae33659c7bcc Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Sun, 1 Jun 2025 13:52:21 +0000
Subject: (대표님) 20250601까지 작업사항
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 components/data-table/expandable-data-table.tsx | 707 ++++++++++++++++--------
 1 file changed, 486 insertions(+), 221 deletions(-)

(limited to 'components/data-table/expandable-data-table.tsx')

diff --git a/components/data-table/expandable-data-table.tsx b/components/data-table/expandable-data-table.tsx
index 9005e2fb..112e9448 100644
--- a/components/data-table/expandable-data-table.tsx
+++ b/components/data-table/expandable-data-table.tsx
@@ -5,7 +5,7 @@ import { flexRender, type Table as TanstackTable } from "@tanstack/react-table"
 import { ChevronRight, ChevronUp, ChevronDown } from "lucide-react"
 
 import { cn } from "@/lib/utils"
-import { getCommonPinningStyles } from "@/lib/data-table"
+import { getCommonPinningStylesWithBorder, debugPinningInfo } from "@/lib/data-table"
 import {
   Table,
   TableBody,
@@ -26,18 +26,13 @@ interface ExpandableDataTableProps<TData> extends React.HTMLAttributes<HTMLDivEl
   expandedRows?: Set<string>
   setExpandedRows?: React.Dispatch<React.SetStateAction<Set<string>>>
   renderExpandedContent?: (row: TData) => React.ReactNode
-  expandable?: boolean // 확장 기능 활성화 여부
+  expandable?: boolean
   maxHeight?: string | number
-  expandedRowClassName?: string // 확장된 행의 커스텀 클래스
+  expandedRowClassName?: string
+  debug?: boolean // 디버깅 옵션 추가
+  simpleExpansion?: boolean // 🎯 간단한 확장 방식 선택 옵션 추가
 }
 
-/**
- * 확장 가능한 데이터 테이블 - 행 확장 시 바로 아래에 컨텐츠 표시
- * 개선사항:
- * - 가로스크롤과 확장된 내용 독립성 보장
- * - 동적 높이 계산으로 세로스크롤 문제 해결
- * - 향상된 접근성 및 키보드 네비게이션
- */
 export function ExpandableDataTable<TData>({
   table,
   floatingBar = null,
@@ -49,6 +44,8 @@ export function ExpandableDataTable<TData>({
   expandable = false,
   maxHeight,
   expandedRowClassName,
+  debug = false,
+  simpleExpansion = false, // 🎯 기본값 false (전체 확장 방식)
   children,
   className,
   ...props
@@ -56,11 +53,228 @@ export function ExpandableDataTable<TData>({
 
   useAutoSizeColumns(table, autoSizeColumns)
 
-  // 스크롤 컨테이너 참조
-  const scrollContainerRef = React.useRef<HTMLDivElement>(null)
+  const containerRef = React.useRef<HTMLDivElement>(null)
+  const scrollRef = React.useRef<HTMLDivElement>(null) // 🎯 스크롤 컨테이너 ref 추가
   const [expandedHeights, setExpandedHeights] = React.useState<Map<string, number>>(new Map())
+  const [isScrolled, setIsScrolled] = React.useState(false)
+  const [isScrolledToEnd, setIsScrolledToEnd] = React.useState(true) // 🎯 초기값을 true로 설정 (아직 계산 안됨)
+  const [isInitialized, setIsInitialized] = React.useState(false) // 🎯 초기화 상태 추가
+  const [containerWidth, setContainerWidth] = React.useState<number>(0)
 
-  // 행 확장/축소 핸들러 (개선된 버전)
+  // 🎯 컨테이너 너비 감지 (패딩 제외된 실제 사용 가능한 너비)
+  React.useEffect(() => {
+    if (!containerRef.current) return
+
+    const updateContainerWidth = () => {
+      if (containerRef.current) {
+        // clientWidth는 패딩을 제외한 실제 사용 가능한 너비
+        setContainerWidth(containerRef.current.clientWidth)
+      }
+    }
+
+    const resizeObserver = new ResizeObserver(updateContainerWidth)
+    resizeObserver.observe(containerRef.current)
+    
+    // 초기 너비 설정
+    updateContainerWidth()
+
+    return () => resizeObserver.disconnect()
+  }, [])
+
+  // 🎯 초기 스크롤 상태 체크 (더 확실한 초기화)
+  React.useEffect(() => {
+    if (!scrollRef.current) return
+    
+    const checkScrollState = () => {
+      if (scrollRef.current) {
+        const { scrollLeft, scrollWidth, clientWidth } = scrollRef.current
+        const newIsScrolled = scrollLeft > 0
+        const newIsScrolledToEnd = Math.abs(scrollWidth - clientWidth - scrollLeft) < 1
+        
+        setIsScrolled(newIsScrolled)
+        setIsScrolledToEnd(newIsScrolledToEnd)
+        setIsInitialized(true)
+        
+        if (debug) {
+          console.log('Initial scroll check:', {
+            scrollLeft,
+            scrollWidth,
+            clientWidth,
+            newIsScrolled,
+            newIsScrolledToEnd,
+            canScroll: scrollWidth > clientWidth
+          })
+        }
+      }
+    }
+    
+    // 즉시 체크
+    checkScrollState()
+    
+    // 짧은 지연 후 재체크 (테이블 렌더링 완료 대기)
+    const timeouts = [50, 100, 200].map(delay => 
+      setTimeout(checkScrollState, delay)
+    )
+    
+    // ResizeObserver로 테이블 크기 변화 감지
+    const resizeObserver = new ResizeObserver(() => {
+      setTimeout(checkScrollState, 10)
+    })
+    resizeObserver.observe(scrollRef.current)
+    
+    return () => {
+      timeouts.forEach(clearTimeout)
+      resizeObserver.disconnect()
+    }
+  }, [table, debug])
+
+  // 🎯 데이터 변경 시 스크롤 상태 재설정
+  React.useEffect(() => {
+    setIsInitialized(false) // 🎯 데이터 변경 시 초기화 상태 리셋
+    setIsScrolledToEnd(true) // 🎯 안전한 기본값으로 리셋
+  }, [table.getRowModel().rows.length])
+
+  const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
+    const scrollLeft = e.currentTarget.scrollLeft
+    const scrollWidth = e.currentTarget.scrollWidth
+    const clientWidth = e.currentTarget.clientWidth
+    
+    // 🎯 왼쪽으로부터 스크롤 상태 (왼쪽 핀용)
+    setIsScrolled(scrollLeft > 0)
+    
+    // 🎯 오른쪽 끝까지 스크롤 상태 (오른쪽 핀용)
+    const isAtEnd = Math.abs(scrollWidth - clientWidth - scrollLeft) < 1 // 1px 오차 허용
+    setIsScrolledToEnd(isAtEnd)
+    setIsInitialized(true) // 🎯 스크롤 이벤트 발생 시 초기화 완료
+    
+    if (debug) {
+      console.log('Scroll state:', {
+        scrollLeft,
+        scrollWidth,
+        clientWidth,
+        isScrolled: scrollLeft > 0,
+        isScrolledToEnd: isAtEnd,
+        remainingScroll: scrollWidth - clientWidth - scrollLeft
+      })
+    }
+  }
+
+  // 🔧 개선된 핀 스타일 함수 (좌/우 핀 구분 처리)
+  const getPinnedStyle = React.useCallback((column: any, isHeader: boolean = false) => {
+    if (debug) {
+      debugPinningInfo(column)
+    }
+
+    try {
+      const baseStyle = getCommonPinningStylesWithBorder({ 
+        column, 
+        withBorder: true 
+      })
+      
+      const pinnedSide = column.getIsPinned()
+      
+      if (!pinnedSide) {
+        // width를 제외한 나머지 스타일만 반환
+        const { width, ...restStyle } = baseStyle
+        // 헤더인 경우 핀되지 않았어도 배경 필요 (sticky 때문에)
+        return {
+          ...restStyle,
+          ...(isHeader && {
+            background: "hsl(var(--background))",
+            transition: "none",
+          }),
+        }
+      }
+
+      // 확장 버튼이 있을 때 left pin된 컬럼들을 오른쪽으로 이동
+      let leftPosition = baseStyle.left
+      if (expandable && pinnedSide === "left") {
+        const expandButtonWidth = 40 // w-10 = 40px
+        if (typeof baseStyle.left === 'string') {
+          const currentLeft = parseFloat(baseStyle.left.replace('px', ''))
+          leftPosition = `${currentLeft + expandButtonWidth}px`
+        } else if (typeof baseStyle.left === 'number') {
+          leftPosition = `${baseStyle.left + expandButtonWidth}px`
+        } else {
+          leftPosition = `${expandButtonWidth}px`
+        }
+      }
+      
+      // 🎯 핀 위치에 따른 배경 결정
+      let shouldShowBackground = false
+      if (isHeader) {
+        // 헤더는 항상 배경 표시
+        shouldShowBackground = true
+      } else {
+        // 바디 셀의 경우 핀 위치에 따라 다른 조건 적용
+        if (pinnedSide === "left") {
+          // 왼쪽 핀: 오른쪽으로 스크롤했을 때 배경 표시
+          shouldShowBackground = isScrolled
+        } else if (pinnedSide === "right") {
+          // 오른쪽 핀: 초기화 전이거나 오른쪽 끝까지 스크롤하지 않았을 때 배경 표시
+          shouldShowBackground = !isInitialized || !isScrolledToEnd
+        }
+      }
+      
+      // width를 제외한 스타일 적용
+      const { width, ...restBaseStyle } = baseStyle
+      
+      const finalStyle = {
+        ...restBaseStyle,
+        left: leftPosition,
+        background: shouldShowBackground 
+          ? "hsl(var(--background))" 
+          : "transparent",
+        transition: isHeader ? "none" : "background-color 0.15s ease-out",
+      }
+
+      if (debug) {
+        console.log("Final pinned style:", {
+          columnId: column.id,
+          pinnedSide,
+          isHeader,
+          isScrolled,
+          isScrolledToEnd,
+          isInitialized,
+          shouldShowBackground,
+          finalStyle
+        })
+      }
+
+      return finalStyle
+    } catch (error) {
+      console.error("Error in getPinnedStyle:", error)
+      // fallback 스타일
+      return {
+        position: 'relative' as const,
+        ...(isHeader && {
+          background: "hsl(var(--background))",
+        }),
+      }
+    }
+  }, [expandable, isScrolled, isScrolledToEnd, isInitialized, debug])
+
+  // 확장 버튼용 스타일 (안정성 개선)
+  const getExpandButtonStyle = React.useCallback(() => {
+    return {
+      position: 'sticky' as const,
+      left: 0,
+      zIndex: 1,
+      background: "hsl(var(--background))",
+      minWidth: '40px',
+      maxWidth: '40px',
+      width: '40px',
+    }
+  }, [])
+
+  // 🎯 테이블 총 너비 계산
+  const getTableWidth = React.useCallback(() => {
+    const expandButtonWidth = expandable ? 40 : 0
+    const totalSize = table.getCenterTotalSize() + table.getLeftTotalSize() + table.getRightTotalSize()
+    return Math.max(totalSize + expandButtonWidth, 800) // 최소 800px 보장
+  }, [table, expandable])
+
+  // 행 확장/축소 핸들러
   const toggleRowExpansion = React.useCallback((rowId: string, event?: React.MouseEvent) => {
     if (!setExpandedRows) return
     
@@ -71,7 +285,6 @@ export function ExpandableDataTable<TData>({
     const newExpanded = new Set(expandedRows)
     if (newExpanded.has(rowId)) {
       newExpanded.delete(rowId)
-      // 높이 정보도 제거
       setExpandedHeights(prev => {
         const newHeights = new Map(prev)
         newHeights.delete(rowId)
@@ -91,7 +304,66 @@ export function ExpandableDataTable<TData>({
     }
   }, [toggleRowExpansion])
 
-  // 확장된 내용의 높이 측정 및 업데이트
+  // 🎯 확장된 내용 스타일 계산 함수 (컨테이너 너비 기준)
+  const getExpandedContentStyle = React.useCallback(() => {
+    const expandButtonWidth = expandable ? 40 : 0
+    const availableWidth = containerWidth || 800 // 🎯 컨테이너 너비 사용 (fallback 800px)
+    const contentWidth = availableWidth - expandButtonWidth - 32 // 버튼 + 여백(16px * 2) 제외
+    
+    // 🎯 디버그 정보
+    if (debug) {
+      console.log('Expanded content sizing:', {
+        containerWidth,
+        availableWidth,
+        expandButtonWidth,
+        contentWidth,
+        finalWidth: Math.max(contentWidth, 300)
+      })
+    }
+    
+    return {
+      width: `${Math.max(contentWidth, 300)}px`, // 🎯 최소 300px 보장
+      marginLeft: `${expandButtonWidth + 8}px`, // 🎯 확장 버튼 + 여백
+      marginRight: '16px',
+      padding: '12px 16px',
+      backgroundColor: 'hsl(var(--background))',
+      borderRadius: '0 0 6px 6px',
+      boxShadow: '0 2px 4px -1px rgba(0, 0, 0, 0.1)',
+      border: '1px solid hsl(var(--border))',
+      borderTop: 'none',
+      marginBottom: '4px',
+      maxWidth: `${availableWidth - expandButtonWidth - 16}px`, // 🎯 컨테이너 너비 초과 방지
+      overflow: 'auto',
+    }
+  }, [expandable, containerWidth, debug])
+
+  // 🎯 간단한 확장 스타일 (컨테이너 너비 기준)
+  const getSimpleExpandedStyle = React.useCallback(() => {
+    const expandButtonWidth = expandable ? 40 : 0
+    const availableWidth = containerWidth || 800
+    const contentWidth = availableWidth - expandButtonWidth - 48 // 버튼 + 여백 제외
+    
+    return {
+      marginLeft: `${expandButtonWidth + 8}px`,
+      marginRight: '16px',
+      width: `${Math.max(contentWidth, 300)}px`,
+      maxWidth: `${availableWidth - expandButtonWidth - 24}px`,
+      padding: '12px 16px',
+      backgroundColor: 'hsl(var(--muted) / 0.5)',
+      borderRadius: '6px',
+      border: '1px solid hsl(var(--border))',
+      marginTop: '8px',
+      marginBottom: '8px',
+      overflow: 'auto',
+    }
+  }, [expandable, containerWidth])
+
+  // 🎯 사용할 확장 스타일 선택 (props로 제어)
+  const useSimpleExpansion = simpleExpansion
+  const getExpandedPlaceholderHeight = React.useCallback((contentHeight: number) => {
+    const padding = 24 + 4 // padding (12px * 2) + marginBottom (4px)
+    return Math.max(contentHeight + padding, 220)
+  }, [])
   const updateExpandedHeight = React.useCallback((rowId: string, height: number) => {
     setExpandedHeights(prev => {
       if (prev.get(rowId) !== height) {
@@ -103,22 +375,26 @@ export function ExpandableDataTable<TData>({
     })
   }, [])
 
-  // 컴팩트 모드를 위한 클래스 정의 (개선된 버전)
+  // 컴팩트 모드 스타일
   const compactStyles = compact ? {
     row: "h-7",
     cell: "py-1 px-2 text-sm",
+    header: "py-1 px-2 text-sm",
+    headerRow: "h-8",
     expandedCell: "py-2 px-4",
     groupRow: "py-1 bg-muted/20 text-sm",
     emptyRow: "h-16",
   } : {
     row: "",
     cell: "",
-    expandedCell: "py-0 px-0", // 패딩 제거하여 확장 컨텐츠가 전체 영역 사용
+    header: "",
+    headerRow: "",
+    expandedCell: "py-0 px-0",
     groupRow: "bg-muted/20",
     emptyRow: "h-24",
   }
 
-  // 확장 버튼 렌더링 함수 (접근성 개선)
+  // 확장 버튼 렌더링
   const renderExpandButton = (rowId: string) => {
     if (!expandable || !setExpandedRows) return null
     
@@ -143,7 +419,7 @@ export function ExpandableDataTable<TData>({
     )
   }
 
-  // 확장된 내용 래퍼 컴포넌트 (높이 측정 기능 포함)
+  // 확장된 내용 래퍼 컴포넌트
   const ExpandedContentWrapper = React.memo<{
     rowId: string
     children: React.ReactNode
@@ -176,244 +452,233 @@ export function ExpandableDataTable<TData>({
     <div className={cn("w-full space-y-2.5", className)} {...props}>
       {children}
       
-      {/* 메인 테이블 컨테이너 - 가로스크롤 문제 해결 */}
       <div 
-        ref={scrollContainerRef}
+        ref={containerRef} // 🎯 컨테이너 wrapper ref (패딩 제외 너비 계산용)
         className="relative rounded-md border"
         style={{ 
-          // maxHeight: maxHeight || '35rem',
-          minHeight: '200px' // 최소 높이 보장
+          minHeight: '200px'
         }}
       >
-        {/* 가로스크롤 영역 */}
-        <div className="overflow-x-auto overflow-y-hidden h-full">
-          {/* 세로스크롤 영역 (확장된 내용 포함) */}
-          <div className="overflow-y-auto h-full">
-            <Table className="[&>thead]:sticky [&>thead]:top-0 [&>thead]:z-10 table-fixed">
-              {/* 테이블 헤더 */}
-              <TableHeader className="bg-background">
-                {table.getHeaderGroups().map((headerGroup) => (
-                  <TableRow key={headerGroup.id} className={compact ? "h-8" : ""}>
-                    {/* 확장 버튼 컬럼 헤더 */}
-                    {expandable && (
-                      <TableHead 
-                        className={cn("w-10 bg-background", compact ? "py-1 px-2" : "")}
-                        style={{ position: 'sticky', left: 0, zIndex: 11 }}
-                      />
-                    )}
-                    
-                    {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={cn(
-                            compact ? "py-1 px-2 text-sm" : "",
-                            "bg-background"
-                          )}
-                          style={{
-                            ...getCommonPinningStyles({ column: header.column }),
-                            width: header.getSize(),
-                          }}
-                        >
-                          <div style={{ position: "relative" }}>
-                            {header.isPlaceholder
-                              ? null
-                              : flexRender(
-                                header.column.columnDef.header,
-                                header.getContext()
-                              )}
+        <div 
+          ref={scrollRef} // 🎯 스크롤 컨테이너 ref 연결
+          className="overflow-auto"
+          style={{ maxHeight: maxHeight || '34rem' }}
+          onScroll={handleScroll} // 🎯 스크롤 이벤트 핸들러
+        >
+          <Table 
+            className="[&>thead]:sticky [&>thead]:top-0 [&>thead]:z-10"
+            style={{ 
+              width: getTableWidth(), // 🎯 동적 너비 계산
+              minWidth: '100%'
+            }}
+          >
+            <TableHeader className="bg-background">
+              {table.getHeaderGroups().map((headerGroup) => (
+                <TableRow key={headerGroup.id} className={compactStyles.headerRow}>
+                  {expandable && (
+                    <TableHead 
+                      className={cn("w-10", compactStyles.header)}
+                      style={getExpandButtonStyle()}
+                    />
+                  )}
+                  
+                  {headerGroup.headers.map((header) => {
+                    if (header.column.getIsGrouped()) {
+                      return null
+                    }
 
-                            {header.column.getCanResize() && (
-                              <DataTableResizer header={header} />
+                    return (
+                      <TableHead
+                        key={header.id}
+                        colSpan={header.colSpan}
+                        data-column-id={header.column.id}
+                        className={cn(
+                          compactStyles.header,
+                          "bg-background"
+                        )}
+                        style={{
+                          ...getPinnedStyle(header.column, true), // 🎯 동적 스타일
+                          width: header.getSize() // 🎯 width 별도 설정
+                        }}
+                      >
+                        <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) => {
-                    const isExpanded = expandedRows.has(row.id)
-                    const expandedHeight = expandedHeights.get(row.id) || 0
-                    
-                    // 그룹핑 헤더 Row
-                    if (row.getIsGrouped()) {
-                      const groupingColumnId = row.groupingColumnId ?? ""
-                      const groupingColumn = table.getColumn(groupingColumnId)
-
-                      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 + (expandable ? 1 : 0)}
-                            className={compact ? "py-1 px-2" : ""}
-                          >
-                            {row.getCanExpand() && (
-                              <button
-                                onClick={row.getToggleExpandedHandler()}
-                                className="inline-flex items-center justify-center mr-2 w-5 h-5 hover:bg-gray-100 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
-                                style={{
-                                  marginLeft: `${row.depth * 1.5}rem`,
-                                }}
-                                aria-label={row.getIsExpanded() ? "그룹 축소" : "그룹 확장"}
-                              >
-                                {row.getIsExpanded() ? (
-                                  <ChevronUp size={compact ? 14 : 16} />
-                                ) : (
-                                  <ChevronRight size={compact ? 14 : 16} />
-                                )}
-                              </button>
-                            )}
+                          {header.column.getCanResize() && (
+                            <DataTableResizer header={header} />
+                          )}
+                        </div>
+                      </TableHead>
+                    )
+                  })}
+                </TableRow>
+              ))}
+            </TableHeader>
 
-                            <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>
-                      )
+            <TableBody>
+              {table.getRowModel().rows?.length ? (
+                table.getRowModel().rows.map((row) => {
+                  const isExpanded = expandedRows.has(row.id)
+                  const expandedHeight = expandedHeights.get(row.id) || 0
+                  
+                  if (row.getIsGrouped()) {
+                    const groupingColumnId = row.groupingColumnId ?? ""
+                    const groupingColumn = table.getColumn(groupingColumnId)
+
+                    let columnLabel = groupingColumnId
+                    if (groupingColumn) {
+                      const headerDef = groupingColumn.columnDef.meta?.excelHeader
+                      if (typeof headerDef === "string") {
+                        columnLabel = headerDef
+                      }
                     }
 
-                    // 일반 Row와 확장된 컨텐츠를 함께 렌더링
                     return (
-                      <React.Fragment key={row.id}>
-                        {/* 메인 데이터 행 */}
-                        <TableRow
-                          className={cn(
-                            compactStyles.row,
-                            isExpanded && "bg-muted/30 border-b-0"
-                          )}
-                          data-state={row.getIsSelected() && "selected"}
+                      <TableRow
+                        key={row.id}
+                        className={compactStyles.groupRow}
+                        data-state={row.getIsExpanded() && "expanded"}
+                      >
+                        <TableCell 
+                          colSpan={table.getVisibleFlatColumns().length + (expandable ? 1 : 0)}
+                          className={compact ? "py-1 px-2" : ""}
                         >
-                          {/* 확장 버튼 셀 */}
-                          {expandable && (
-                            <TableCell 
-                              className={cn("w-10", compactStyles.cell)}
-                              style={{ 
-                                position: 'sticky', 
-                                left: 0, 
-                                zIndex: 1,
-                                backgroundColor: isExpanded ? 'rgb(248 250 252)' : 'white'
+                          {row.getCanExpand() && (
+                            <button
+                              onClick={row.getToggleExpandedHandler()}
+                              className="inline-flex items-center justify-center mr-2 w-5 h-5 hover:bg-gray-100 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
+                              style={{
+                                marginLeft: `${row.depth * 1.5}rem`,
                               }}
+                              aria-label={row.getIsExpanded() ? "그룹 축소" : "그룹 확장"}
                             >
-                              {renderExpandButton(row.id)}
-                            </TableCell>
+                              {row.getIsExpanded() ? (
+                                <ChevronUp size={compact ? 14 : 16} />
+                              ) : (
+                                <ChevronRight size={compact ? 14 : 16} />
+                              )}
+                            </button>
                           )}
 
-                          {/* 데이터 셀들 */}
-                          {row.getVisibleCells().map((cell) => {
-                            if (cell.column.getIsGrouped()) {
-                              return null
-                            }
-
-                            return (
-                              <TableCell
-                                key={cell.id}
-                                data-column-id={cell.column.id}
-                                className={compactStyles.cell}
-                                style={{
-                                  ...getCommonPinningStyles({ column: cell.column }),
-                                  width: cell.column.getSize(),
-                                }}
-                              >
-                                {flexRender(
-                                  cell.column.columnDef.cell,
-                                  cell.getContext()
-                                )}
-                              </TableCell>
-                            )
-                          })}
-                        </TableRow>
+                          <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>
+                    )
+                  }
+
+                  return (
+                    <React.Fragment key={row.id}>
+                      <TableRow
+                        className={cn(
+                          compactStyles.row,
+                          isExpanded && "bg-muted/30 border-b-0"
+                        )}
+                        data-state={row.getIsSelected() && "selected"}
+                      >
+                        {expandable && (
+                          <TableCell 
+                            className={cn("w-10", compactStyles.cell)}
+                            style={getExpandButtonStyle()}
+                          >
+                            {renderExpandButton(row.id)}
+                          </TableCell>
+                        )}
 
-                        {/* 확장된 컨텐츠 행 - 가로스크롤 독립성 보장 */}
-                        {isExpanded && renderExpandedContent && (
-                          <TableRow className="hover:bg-transparent">
+                        {row.getVisibleCells().map((cell) => {
+                          if (cell.column.getIsGrouped()) {
+                            return null
+                          }
+
+                          return (
                             <TableCell
-                              colSpan={table.getVisibleFlatColumns().length + (expandable ? 1 : 0)}
-                              className={cn(
-                                compactStyles.expandedCell,
-                                "border-t-0 relative",
-                                expandedRowClassName
-                              )}
+                              key={cell.id}
+                              data-column-id={cell.column.id}
+                              className={compactStyles.cell}
                               style={{
-                                minHeight: expandedHeight || 'auto',
+                                ...getPinnedStyle(cell.column, false), // 🎯 동적 스타일
+                                width: cell.column.getSize() // 🎯 width 별도 설정
                               }}
                             >
-                              {/* 확장된 내용을 위한 고정 폭 컨테이너 */}
+                              {flexRender(
+                                cell.column.columnDef.cell,
+                                cell.getContext()
+                              )}
+                            </TableCell>
+                          )
+                        })}
+                      </TableRow>
+
+                      {isExpanded && renderExpandedContent && (
+                        <TableRow className="hover:bg-transparent">
+                          <TableCell
+                            colSpan={table.getVisibleFlatColumns().length + (expandable ? 1 : 0)}
+                            className={cn(
+                              compactStyles.expandedCell,
+                              "border-t-0 relative",
+                              expandedRowClassName
+                            )}
+                            style={{
+                              minHeight: expandedHeight || 'auto',
+                            }}
+                          >
+                            {useSimpleExpansion ? (
+                              // 🎯 간단한 확장 방식: 테이블 내부에서만 확장
+                              <div style={getSimpleExpandedStyle()}>
+                                <ExpandedContentWrapper rowId={row.id}>
+                                  {renderExpandedContent(row.original)}
+                                </ExpandedContentWrapper>
+                              </div>
+                            ) : (
+                              // 🎯 전체 화면 확장 방식: 테이블 너비 기준으로 개선
                               <div className="relative w-full">
-                                {/* 가로스크롤과 독립적인 확장 영역 */}
                                 <div 
-                                  className="absolute left-0 right-0 top-0 border-t"
-                                  style={{
-                                    width: '80vw',
-                                    marginLeft: 'calc(-48vw + 50%)',
-                                  }}
+                                  className="absolute top-0"
+                                  style={getExpandedContentStyle()}
                                 >
-                                  <div className="max-w-none mx-auto">
-                                    <ExpandedContentWrapper rowId={row.id}>
-                                      {renderExpandedContent(row.original)}
-                                    </ExpandedContentWrapper>
-                                  </div>
+                                  <ExpandedContentWrapper rowId={row.id}>
+                                    {renderExpandedContent(row.original)}
+                                  </ExpandedContentWrapper>
                                 </div>
                                 
-                                {/* 높이 유지를 위한 스페이서 */}
                                 <div 
                                   className="opacity-0 pointer-events-none"
-                                  style={{ height: Math.max(expandedHeight, 200) }}
+                                  style={{ height: getExpandedPlaceholderHeight(expandedHeight) }}
                                 />
                               </div>
-                            </TableCell>
-                          </TableRow>
-                        )}
-                      </React.Fragment>
-                    )
-                  })
-                ) : (
-                  // 데이터가 없을 때
-                  <TableRow>
-                    <TableCell
-                      colSpan={table.getAllColumns().length + (expandable ? 1 : 0)}
-                      className={compactStyles.emptyRow + " text-center"}
-                    >
-                      No results.
-                    </TableCell>
-                  </TableRow>
-                )}
-              </TableBody>
-            </Table>
-          </div>
+                            )}
+                          </TableCell>
+                        </TableRow>
+                      )}
+                    </React.Fragment>
+                  )
+                })
+              ) : (
+                <TableRow>
+                  <TableCell
+                    colSpan={table.getAllColumns().length + (expandable ? 1 : 0)}
+                    className={compactStyles.emptyRow + " text-center"}
+                  >
+                    No results.
+                  </TableCell>
+                </TableRow>
+              )}
+            </TableBody>
+          </Table>
         </div>
       </div>
 
       <div className="flex flex-col gap-2.5">
-        {/* Pagination */}
         <DataTablePagination table={table} />
-
-        {/* Floating Bar (선택된 행 있을 때) */}
         {table.getFilteredSelectedRowModel().rows.length > 0 && floatingBar}
       </div>
     </div>
-- 
cgit v1.2.3

