검색필터 수정사항
This commit is contained in:
parent
c557fc5d56
commit
f15c1fa114
|
|
@ -25,7 +25,7 @@ export const ResponsiveDesignerContainer: React.FC<ResponsiveDesignerContainerPr
|
||||||
onScaleChange,
|
onScaleChange,
|
||||||
}) => {
|
}) => {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const [viewMode, setViewMode] = useState<DesignerViewMode>("fit");
|
const [viewMode, setViewMode] = useState<DesignerViewMode>("original");
|
||||||
const [customScale, setCustomScale] = useState(1);
|
const [customScale, setCustomScale] = useState(1);
|
||||||
const containerSize = useContainerSize(containerRef);
|
const containerSize = useContainerSize(containerRef);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -317,7 +317,7 @@ export const AdvancedSearchFilters: React.FC<AdvancedSearchFiltersProps> = ({
|
||||||
}).length;
|
}).length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("space-y-4", className)}>
|
<div className={cn("space-y-2", className)}>
|
||||||
{/* 필터 헤더 */}
|
{/* 필터 헤더 */}
|
||||||
<div className="text-muted-foreground flex items-center gap-2 text-sm">
|
<div className="text-muted-foreground flex items-center gap-2 text-sm">
|
||||||
<Search className="h-3 w-3" />
|
<Search className="h-3 w-3" />
|
||||||
|
|
@ -339,7 +339,7 @@ export const AdvancedSearchFilters: React.FC<AdvancedSearchFiltersProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={filter.columnName} className={`space-y-1 ${getFilterWidth()}`}>
|
<div key={filter.columnName} className={`space-y-0.5 ${getFilterWidth()}`}>
|
||||||
<label className="text-muted-foreground text-xs font-medium">{filter.label}</label>
|
<label className="text-muted-foreground text-xs font-medium">{filter.label}</label>
|
||||||
{renderFilter(filter)}
|
{renderFilter(filter)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,188 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import { ArrowUp, ArrowDown, ArrowUpDown } from "lucide-react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { ColumnConfig } from "./types";
|
||||||
|
|
||||||
|
interface SingleTableWithStickyProps {
|
||||||
|
visibleColumns: ColumnConfig[];
|
||||||
|
data: Record<string, any>[];
|
||||||
|
columnLabels: Record<string, string>;
|
||||||
|
sortColumn: string | null;
|
||||||
|
sortDirection: "asc" | "desc";
|
||||||
|
tableConfig: any;
|
||||||
|
isDesignMode: boolean;
|
||||||
|
isAllSelected: boolean;
|
||||||
|
handleSort: (columnName: string) => void;
|
||||||
|
handleSelectAll: (checked: boolean) => void;
|
||||||
|
handleRowClick: (row: any) => void;
|
||||||
|
renderCheckboxCell: (row: any, index: number) => React.ReactNode;
|
||||||
|
formatCellValue: (value: any, format?: string, columnName?: string) => string;
|
||||||
|
getColumnWidth: (column: ColumnConfig) => number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SingleTableWithSticky: React.FC<SingleTableWithStickyProps> = ({
|
||||||
|
visibleColumns,
|
||||||
|
data,
|
||||||
|
columnLabels,
|
||||||
|
sortColumn,
|
||||||
|
sortDirection,
|
||||||
|
tableConfig,
|
||||||
|
isDesignMode,
|
||||||
|
isAllSelected,
|
||||||
|
handleSort,
|
||||||
|
handleSelectAll,
|
||||||
|
handleRowClick,
|
||||||
|
renderCheckboxCell,
|
||||||
|
formatCellValue,
|
||||||
|
getColumnWidth,
|
||||||
|
}) => {
|
||||||
|
const checkboxConfig = tableConfig.checkbox || {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative h-full w-full overflow-auto">
|
||||||
|
<Table className="w-full">
|
||||||
|
<TableHeader className={tableConfig.stickyHeader ? "sticky top-0 z-20 bg-white" : ""}>
|
||||||
|
<TableRow>
|
||||||
|
{visibleColumns.map((column, colIndex) => {
|
||||||
|
// 왼쪽 고정 컬럼들의 누적 너비 계산
|
||||||
|
const leftFixedWidth = visibleColumns
|
||||||
|
.slice(0, colIndex)
|
||||||
|
.filter((col) => col.fixed === "left")
|
||||||
|
.reduce((sum, col) => sum + getColumnWidth(col), 0);
|
||||||
|
|
||||||
|
// 오른쪽 고정 컬럼들의 누적 너비 계산
|
||||||
|
const rightFixedColumns = visibleColumns.filter((col) => col.fixed === "right");
|
||||||
|
const rightFixedIndex = rightFixedColumns.findIndex((col) => col.columnName === column.columnName);
|
||||||
|
const rightFixedWidth =
|
||||||
|
rightFixedIndex >= 0
|
||||||
|
? rightFixedColumns.slice(rightFixedIndex + 1).reduce((sum, col) => sum + getColumnWidth(col), 0)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableHead
|
||||||
|
key={column.columnName}
|
||||||
|
className={cn(
|
||||||
|
column.columnName === "__checkbox__"
|
||||||
|
? "h-10 border-b px-4 py-2 text-center align-middle"
|
||||||
|
: "h-10 cursor-pointer border-b px-4 py-2 text-left align-middle font-medium whitespace-nowrap text-gray-900 select-none",
|
||||||
|
`text-${column.align}`,
|
||||||
|
column.sortable && "hover:bg-gray-50",
|
||||||
|
// 고정 컬럼 스타일
|
||||||
|
column.fixed === "left" && "sticky z-10 border-r bg-white shadow-sm",
|
||||||
|
column.fixed === "right" && "sticky z-10 border-l bg-white shadow-sm",
|
||||||
|
// 숨김 컬럼 스타일 (디자인 모드에서만)
|
||||||
|
isDesignMode && column.hidden && "bg-gray-100/50 opacity-40",
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
width: getColumnWidth(column),
|
||||||
|
minWidth: getColumnWidth(column),
|
||||||
|
maxWidth: getColumnWidth(column),
|
||||||
|
// sticky 위치 설정
|
||||||
|
...(column.fixed === "left" && { left: leftFixedWidth }),
|
||||||
|
...(column.fixed === "right" && { right: rightFixedWidth }),
|
||||||
|
}}
|
||||||
|
onClick={() => column.sortable && handleSort(column.columnName)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{column.columnName === "__checkbox__" ? (
|
||||||
|
checkboxConfig.selectAll && (
|
||||||
|
<Checkbox checked={isAllSelected} onCheckedChange={handleSelectAll} aria-label="전체 선택" />
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span className="flex-1 truncate">
|
||||||
|
{columnLabels[column.columnName] || column.displayName || column.columnName}
|
||||||
|
</span>
|
||||||
|
{column.sortable && (
|
||||||
|
<span className="ml-1">
|
||||||
|
{sortColumn === column.columnName ? (
|
||||||
|
sortDirection === "asc" ? (
|
||||||
|
<ArrowUp className="h-3 w-3 text-blue-600" />
|
||||||
|
) : (
|
||||||
|
<ArrowDown className="h-3 w-3 text-blue-600" />
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<ArrowUpDown className="h-3 w-3 text-gray-400" />
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TableHead>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
|
||||||
|
<TableBody>
|
||||||
|
{data.length === 0 ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={visibleColumns.length} className="py-8 text-center text-gray-500">
|
||||||
|
데이터가 없습니다
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : (
|
||||||
|
data.map((row, index) => (
|
||||||
|
<TableRow
|
||||||
|
key={`row-${index}`}
|
||||||
|
className={cn(
|
||||||
|
"h-10 cursor-pointer border-b leading-none",
|
||||||
|
tableConfig.tableStyle?.hoverEffect && "hover:bg-gray-50",
|
||||||
|
tableConfig.tableStyle?.alternateRows && index % 2 === 1 && "bg-gray-50/30",
|
||||||
|
)}
|
||||||
|
style={{ minHeight: "40px", height: "40px", lineHeight: "1" }}
|
||||||
|
onClick={() => handleRowClick(row)}
|
||||||
|
>
|
||||||
|
{visibleColumns.map((column, colIndex) => {
|
||||||
|
// 왼쪽 고정 컬럼들의 누적 너비 계산
|
||||||
|
const leftFixedWidth = visibleColumns
|
||||||
|
.slice(0, colIndex)
|
||||||
|
.filter((col) => col.fixed === "left")
|
||||||
|
.reduce((sum, col) => sum + getColumnWidth(col), 0);
|
||||||
|
|
||||||
|
// 오른쪽 고정 컬럼들의 누적 너비 계산
|
||||||
|
const rightFixedColumns = visibleColumns.filter((col) => col.fixed === "right");
|
||||||
|
const rightFixedIndex = rightFixedColumns.findIndex((col) => col.columnName === column.columnName);
|
||||||
|
const rightFixedWidth =
|
||||||
|
rightFixedIndex >= 0
|
||||||
|
? rightFixedColumns.slice(rightFixedIndex + 1).reduce((sum, col) => sum + getColumnWidth(col), 0)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableCell
|
||||||
|
key={`cell-${column.columnName}`}
|
||||||
|
className={cn(
|
||||||
|
"h-10 px-4 py-2 align-middle text-sm whitespace-nowrap",
|
||||||
|
`text-${column.align}`,
|
||||||
|
// 고정 컬럼 스타일
|
||||||
|
column.fixed === "left" && "sticky z-10 border-r bg-white",
|
||||||
|
column.fixed === "right" && "sticky z-10 border-l bg-white",
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
minHeight: "40px",
|
||||||
|
height: "40px",
|
||||||
|
verticalAlign: "middle",
|
||||||
|
// sticky 위치 설정
|
||||||
|
...(column.fixed === "left" && { left: leftFixedWidth }),
|
||||||
|
...(column.fixed === "right" && { right: rightFixedWidth }),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{column.columnName === "__checkbox__"
|
||||||
|
? renderCheckboxCell(row, index)
|
||||||
|
: formatCellValue(row[column.columnName], column.format, column.columnName) || "\u00A0"}
|
||||||
|
</TableCell>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -23,6 +23,7 @@ import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { AdvancedSearchFilters } from "@/components/screen/filters/AdvancedSearchFilters";
|
import { AdvancedSearchFilters } from "@/components/screen/filters/AdvancedSearchFilters";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
import { SingleTableWithSticky } from "./SingleTableWithSticky";
|
||||||
|
|
||||||
export interface TableListComponentProps {
|
export interface TableListComponentProps {
|
||||||
component: any;
|
component: any;
|
||||||
|
|
@ -113,39 +114,67 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
maxBatchSize: 5,
|
maxBatchSize: 5,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 높이 계산 함수
|
// 높이 계산 함수 (메모이제이션)
|
||||||
const calculateOptimalHeight = () => {
|
const optimalHeight = useMemo(() => {
|
||||||
// 50개 이상일 때는 20개 기준으로 높이 고정
|
// 50개 이상일 때는 20개 기준으로 높이 고정하고 스크롤 생성
|
||||||
const displayPageSize = localPageSize >= 50 ? 20 : localPageSize;
|
// 50개 미만일 때는 실제 데이터 개수에 맞춰서 스크롤 없이 표시
|
||||||
const headerHeight = 48; // 테이블 헤더
|
const actualDataCount = Math.min(data.length, localPageSize);
|
||||||
const rowHeight = 40; // 각 행 높이 (normal)
|
const displayPageSize = localPageSize >= 50 ? 20 : Math.max(actualDataCount, 5);
|
||||||
const searchHeight = tableConfig.filter?.enabled ? 48 : 0; // 검색 영역
|
|
||||||
const footerHeight = tableConfig.showFooter ? 56 : 0; // 페이지네이션
|
|
||||||
const padding = 8; // 여백
|
|
||||||
|
|
||||||
return headerHeight + displayPageSize * rowHeight + searchHeight + footerHeight + padding;
|
const headerHeight = 50; // 테이블 헤더
|
||||||
};
|
const rowHeight = 42; // 각 행 높이
|
||||||
|
const searchHeight = tableConfig.filter?.enabled ? 80 : 0; // 검색 영역
|
||||||
|
const footerHeight = tableConfig.showFooter ? 60 : 0; // 페이지네이션
|
||||||
|
const titleHeight = tableConfig.showHeader ? 60 : 0; // 제목 영역
|
||||||
|
const padding = 40; // 여백
|
||||||
|
|
||||||
|
const calculatedHeight =
|
||||||
|
titleHeight + searchHeight + headerHeight + displayPageSize * rowHeight + footerHeight + padding;
|
||||||
|
|
||||||
|
console.log("🔍 테이블 높이 계산:", {
|
||||||
|
actualDataCount,
|
||||||
|
localPageSize,
|
||||||
|
displayPageSize,
|
||||||
|
willHaveScroll: localPageSize >= 50,
|
||||||
|
titleHeight,
|
||||||
|
searchHeight,
|
||||||
|
headerHeight,
|
||||||
|
rowHeight,
|
||||||
|
footerHeight,
|
||||||
|
padding,
|
||||||
|
calculatedHeight,
|
||||||
|
finalHeight: `${calculatedHeight}px`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 추가 디버깅: 실제 데이터 상황
|
||||||
|
console.log("🔍 실제 데이터 상황:", {
|
||||||
|
actualDataLength: data.length,
|
||||||
|
localPageSize,
|
||||||
|
currentPage,
|
||||||
|
totalItems,
|
||||||
|
totalPages,
|
||||||
|
});
|
||||||
|
|
||||||
|
return calculatedHeight;
|
||||||
|
}, [data.length, localPageSize, tableConfig.filter?.enabled, tableConfig.showFooter, tableConfig.showHeader]);
|
||||||
|
|
||||||
// 스타일 계산
|
// 스타일 계산
|
||||||
const componentStyle: React.CSSProperties = {
|
const componentStyle: React.CSSProperties = {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height:
|
height: `${optimalHeight}px`, // 20개 데이터를 모두 보여주는 높이
|
||||||
tableConfig.height === "fixed"
|
minHeight: `${optimalHeight}px`, // 최소 높이 보장
|
||||||
? `${tableConfig.fixedHeight || calculateOptimalHeight()}px`
|
|
||||||
: tableConfig.height === "auto"
|
|
||||||
? `${calculateOptimalHeight()}px`
|
|
||||||
: "100%",
|
|
||||||
...component.style,
|
...component.style,
|
||||||
...style,
|
...style,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
|
boxSizing: "border-box", // 패딩/보더 포함한 크기 계산
|
||||||
};
|
};
|
||||||
|
|
||||||
// 디자인 모드 스타일
|
// 디자인 모드 스타일
|
||||||
if (isDesignMode) {
|
if (isDesignMode) {
|
||||||
componentStyle.border = "1px dashed #cbd5e1";
|
componentStyle.border = "1px dashed #cbd5e1";
|
||||||
componentStyle.borderColor = isSelected ? "#3b82f6" : "#cbd5e1";
|
componentStyle.borderColor = isSelected ? "#3b82f6" : "#cbd5e1";
|
||||||
componentStyle.minHeight = "200px";
|
// minHeight 제거 - 실제 데이터에 맞는 높이 사용
|
||||||
}
|
}
|
||||||
|
|
||||||
// 컬럼 라벨 정보 가져오기
|
// 컬럼 라벨 정보 가져오기
|
||||||
|
|
@ -637,28 +666,8 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
return columns;
|
return columns;
|
||||||
}, [displayColumns, tableConfig.columns, tableConfig.checkbox]);
|
}, [displayColumns, tableConfig.columns, tableConfig.checkbox]);
|
||||||
|
|
||||||
// 컬럼을 고정 위치별로 분류
|
// columnsByPosition은 SingleTableWithSticky에서 사용하지 않으므로 제거
|
||||||
const columnsByPosition = useMemo(() => {
|
// 기존 테이블에서만 필요한 경우 다시 추가 가능
|
||||||
const leftFixed: ColumnConfig[] = [];
|
|
||||||
const rightFixed: ColumnConfig[] = [];
|
|
||||||
const normal: ColumnConfig[] = [];
|
|
||||||
|
|
||||||
visibleColumns.forEach((col) => {
|
|
||||||
if (col.fixed === "left") {
|
|
||||||
leftFixed.push(col);
|
|
||||||
} else if (col.fixed === "right") {
|
|
||||||
rightFixed.push(col);
|
|
||||||
} else {
|
|
||||||
normal.push(col);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 고정 컬럼들은 fixedOrder로 정렬
|
|
||||||
leftFixed.sort((a, b) => (a.fixedOrder || 0) - (b.fixedOrder || 0));
|
|
||||||
rightFixed.sort((a, b) => (a.fixedOrder || 0) - (b.fixedOrder || 0));
|
|
||||||
|
|
||||||
return { leftFixed, rightFixed, normal };
|
|
||||||
}, [visibleColumns]);
|
|
||||||
|
|
||||||
// 가로 스크롤이 필요한지 계산
|
// 가로 스크롤이 필요한지 계산
|
||||||
const needsHorizontalScroll = useMemo(() => {
|
const needsHorizontalScroll = useMemo(() => {
|
||||||
|
|
@ -893,7 +902,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
{/* 고급 검색 필터 - 항상 표시 (컬럼 정보 기반 자동 생성) */}
|
{/* 고급 검색 필터 - 항상 표시 (컬럼 정보 기반 자동 생성) */}
|
||||||
{tableConfig.filter?.enabled && visibleColumns && visibleColumns.length > 0 && (
|
{tableConfig.filter?.enabled && visibleColumns && visibleColumns.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<Separator className="my-2" />
|
<Separator className="my-1" />
|
||||||
<AdvancedSearchFilters
|
<AdvancedSearchFilters
|
||||||
filters={tableConfig.filter?.filters || []} // 설정된 필터 사용, 없으면 자동 생성
|
filters={tableConfig.filter?.filters || []} // 설정된 필터 사용, 없으면 자동 생성
|
||||||
searchValues={searchValues}
|
searchValues={searchValues}
|
||||||
|
|
@ -918,7 +927,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 테이블 컨텐츠 */}
|
{/* 테이블 컨텐츠 */}
|
||||||
<div className={`flex-1 ${localPageSize >= 50 ? "overflow-auto" : "overflow-hidden"}`}>
|
<div className={`w-full ${localPageSize >= 50 ? "flex-1 overflow-auto" : ""}`}>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="flex h-full items-center justify-center">
|
<div className="flex h-full items-center justify-center">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
|
|
@ -934,317 +943,43 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : needsHorizontalScroll ? (
|
) : needsHorizontalScroll ? (
|
||||||
// 가로 스크롤이 필요한 경우 - 고정 컬럼 지원 테이블
|
// 가로 스크롤이 필요한 경우 - 단일 테이블에서 sticky 컬럼 사용
|
||||||
<div className="relative flex h-full">
|
<SingleTableWithSticky
|
||||||
{/* 왼쪽 고정 컬럼 */}
|
visibleColumns={visibleColumns}
|
||||||
{columnsByPosition.leftFixed.length > 0 && (
|
data={data}
|
||||||
<div className="flex-shrink-0 border-r bg-gray-50/50">
|
columnLabels={columnLabels}
|
||||||
<table
|
sortColumn={sortColumn}
|
||||||
className="table-fixed-layout table-auto"
|
sortDirection={sortDirection}
|
||||||
style={{ borderCollapse: "collapse", margin: 0, padding: 0 }}
|
tableConfig={tableConfig}
|
||||||
>
|
isDesignMode={isDesignMode}
|
||||||
<thead className={tableConfig.stickyHeader ? "sticky top-0 z-20 bg-white" : ""}>
|
isAllSelected={isAllSelected}
|
||||||
<tr>
|
handleSort={handleSort}
|
||||||
{columnsByPosition.leftFixed.map((column) => (
|
handleSelectAll={handleSelectAll}
|
||||||
<th
|
handleRowClick={handleRowClick}
|
||||||
key={`fixed-left-${column.columnName}`}
|
renderCheckboxCell={renderCheckboxCell}
|
||||||
className={cn(
|
formatCellValue={formatCellValue}
|
||||||
column.columnName === "__checkbox__"
|
getColumnWidth={getColumnWidth}
|
||||||
? "h-12 border-b px-4 py-3 text-center align-middle"
|
/>
|
||||||
: "h-12 cursor-pointer border-b px-4 py-3 text-left align-middle font-medium whitespace-nowrap text-gray-900 select-none",
|
|
||||||
`text-${column.align}`,
|
|
||||||
column.sortable && "hover:bg-gray-50",
|
|
||||||
// 숨김 컬럼 스타일 (디자인 모드에서만)
|
|
||||||
isDesignMode && column.hidden && "bg-gray-100/50 opacity-40",
|
|
||||||
)}
|
|
||||||
style={{
|
|
||||||
minWidth: `${getColumnWidth(column)}px`,
|
|
||||||
minHeight: "48px",
|
|
||||||
height: "48px",
|
|
||||||
verticalAlign: "middle",
|
|
||||||
lineHeight: "1",
|
|
||||||
boxSizing: "border-box",
|
|
||||||
}}
|
|
||||||
onClick={() => column.sortable && handleSort(column.columnName)}
|
|
||||||
>
|
|
||||||
{column.columnName === "__checkbox__" ? (
|
|
||||||
renderCheckboxHeader()
|
|
||||||
) : (
|
|
||||||
<div className="flex items-center space-x-1">
|
|
||||||
<span className="text-sm">{columnLabels[column.columnName] || column.displayName}</span>
|
|
||||||
{column.sortable && (
|
|
||||||
<div className="flex flex-col">
|
|
||||||
{sortColumn === column.columnName ? (
|
|
||||||
sortDirection === "asc" ? (
|
|
||||||
<ArrowUp className="h-3 w-3" />
|
|
||||||
) : (
|
|
||||||
<ArrowDown className="h-3 w-3" />
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<ArrowUpDown className="h-3 w-3 text-gray-400" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</th>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{data.length === 0 ? (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={columnsByPosition.leftFixed.length} className="py-8 text-center text-gray-500">
|
|
||||||
데이터가 없습니다
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
) : (
|
|
||||||
data.map((row, index) => (
|
|
||||||
<tr
|
|
||||||
key={`fixed-left-row-${index}`}
|
|
||||||
className={cn(
|
|
||||||
"h-12 cursor-pointer border-b leading-none",
|
|
||||||
tableConfig.tableStyle?.hoverEffect && "hover:bg-gray-50",
|
|
||||||
tableConfig.tableStyle?.alternateRows && index % 2 === 1 && "bg-gray-50/50",
|
|
||||||
)}
|
|
||||||
style={{ minHeight: "48px", height: "48px", lineHeight: "1" }}
|
|
||||||
onClick={() => handleRowClick(row)}
|
|
||||||
>
|
|
||||||
{columnsByPosition.leftFixed.map((column) => (
|
|
||||||
<td
|
|
||||||
key={`fixed-left-cell-${column.columnName}`}
|
|
||||||
className={cn(
|
|
||||||
"h-12 px-4 py-3 align-middle text-sm whitespace-nowrap",
|
|
||||||
`text-${column.align}`,
|
|
||||||
)}
|
|
||||||
style={{ minHeight: "48px", height: "48px", verticalAlign: "middle" }}
|
|
||||||
>
|
|
||||||
{column.columnName === "__checkbox__"
|
|
||||||
? renderCheckboxCell(row, index)
|
|
||||||
: formatCellValue(row[column.columnName], column.format, column.columnName) || "\u00A0"}
|
|
||||||
</td>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 스크롤 가능한 중앙 컬럼들 */}
|
|
||||||
<div className="flex-1 overflow-x-auto">
|
|
||||||
<table
|
|
||||||
className="table-fixed-layout w-full table-auto"
|
|
||||||
style={{ borderCollapse: "collapse", margin: 0, padding: 0 }}
|
|
||||||
>
|
|
||||||
<thead className={tableConfig.stickyHeader ? "sticky top-0 z-10 bg-white" : ""}>
|
|
||||||
<tr>
|
|
||||||
{columnsByPosition.normal.map((column) => (
|
|
||||||
<th
|
|
||||||
key={`normal-${column.columnName}`}
|
|
||||||
style={{
|
|
||||||
minWidth: `${getColumnWidth(column)}px`,
|
|
||||||
minHeight: "48px",
|
|
||||||
height: "48px",
|
|
||||||
verticalAlign: "middle",
|
|
||||||
lineHeight: "1",
|
|
||||||
boxSizing: "border-box",
|
|
||||||
}}
|
|
||||||
className={cn(
|
|
||||||
column.columnName === "__checkbox__"
|
|
||||||
? "h-12 border-b px-4 py-3 text-center align-middle"
|
|
||||||
: "cursor-pointer border-b px-4 py-3 text-left align-middle font-medium whitespace-nowrap text-gray-900 select-none",
|
|
||||||
`text-${column.align}`,
|
|
||||||
column.sortable && "hover:bg-gray-50",
|
|
||||||
// 숨김 컬럼 스타일 (디자인 모드에서만)
|
|
||||||
isDesignMode && column.hidden && "bg-gray-100/50 opacity-40",
|
|
||||||
)}
|
|
||||||
onClick={() => column.sortable && handleSort(column.columnName)}
|
|
||||||
>
|
|
||||||
{column.columnName === "__checkbox__" ? (
|
|
||||||
renderCheckboxHeader()
|
|
||||||
) : (
|
|
||||||
<div className="flex items-center space-x-1">
|
|
||||||
<span className="text-sm">{columnLabels[column.columnName] || column.displayName}</span>
|
|
||||||
{column.sortable && (
|
|
||||||
<div className="flex flex-col">
|
|
||||||
{sortColumn === column.columnName ? (
|
|
||||||
sortDirection === "asc" ? (
|
|
||||||
<ArrowUp className="h-3 w-3" />
|
|
||||||
) : (
|
|
||||||
<ArrowDown className="h-3 w-3" />
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<ArrowUpDown className="h-3 w-3 text-gray-400" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</th>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{data.length === 0 ? (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={columnsByPosition.normal.length} className="py-8 text-center text-gray-500">
|
|
||||||
{columnsByPosition.leftFixed.length === 0 && columnsByPosition.rightFixed.length === 0
|
|
||||||
? "데이터가 없습니다"
|
|
||||||
: ""}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
) : (
|
|
||||||
data.map((row, index) => (
|
|
||||||
<tr
|
|
||||||
key={`normal-row-${index}`}
|
|
||||||
className={cn(
|
|
||||||
"cursor-pointer border-b",
|
|
||||||
tableConfig.tableStyle?.hoverEffect && "hover:bg-gray-50",
|
|
||||||
tableConfig.tableStyle?.alternateRows && index % 2 === 1 && "bg-gray-50/50",
|
|
||||||
)}
|
|
||||||
onClick={() => handleRowClick(row)}
|
|
||||||
>
|
|
||||||
{columnsByPosition.normal.map((column) => (
|
|
||||||
<td
|
|
||||||
key={`normal-cell-${column.columnName}`}
|
|
||||||
className={cn(
|
|
||||||
"h-12 px-4 py-3 align-middle text-sm whitespace-nowrap",
|
|
||||||
`text-${column.align}`,
|
|
||||||
)}
|
|
||||||
style={{ minHeight: "48px", height: "48px", verticalAlign: "middle" }}
|
|
||||||
>
|
|
||||||
{column.columnName === "__checkbox__"
|
|
||||||
? renderCheckboxCell(row, index)
|
|
||||||
: formatCellValue(row[column.columnName], column.format, column.columnName) || "\u00A0"}
|
|
||||||
</td>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 오른쪽 고정 컬럼 */}
|
|
||||||
{columnsByPosition.rightFixed.length > 0 && (
|
|
||||||
<div className="flex-shrink-0 border-l bg-gray-50/50">
|
|
||||||
<table
|
|
||||||
className="table-fixed-layout table-auto"
|
|
||||||
style={{ borderCollapse: "collapse", margin: 0, padding: 0 }}
|
|
||||||
>
|
|
||||||
<thead className={tableConfig.stickyHeader ? "sticky top-0 z-20 bg-white" : ""}>
|
|
||||||
<tr>
|
|
||||||
{columnsByPosition.rightFixed.map((column) => (
|
|
||||||
<th
|
|
||||||
key={`fixed-right-${column.columnName}`}
|
|
||||||
className={cn(
|
|
||||||
column.columnName === "__checkbox__"
|
|
||||||
? "h-12 border-b px-4 py-3 text-center align-middle"
|
|
||||||
: "h-12 cursor-pointer border-b px-4 py-3 text-left align-middle font-medium whitespace-nowrap text-gray-900 select-none",
|
|
||||||
`text-${column.align}`,
|
|
||||||
column.sortable && "hover:bg-gray-50",
|
|
||||||
// 숨김 컬럼 스타일 (디자인 모드에서만)
|
|
||||||
isDesignMode && column.hidden && "bg-gray-100/50 opacity-40",
|
|
||||||
)}
|
|
||||||
style={{
|
|
||||||
minWidth: `${getColumnWidth(column)}px`,
|
|
||||||
minHeight: "48px",
|
|
||||||
height: "48px",
|
|
||||||
verticalAlign: "middle",
|
|
||||||
lineHeight: "1",
|
|
||||||
boxSizing: "border-box",
|
|
||||||
}}
|
|
||||||
onClick={() => column.sortable && handleSort(column.columnName)}
|
|
||||||
>
|
|
||||||
{column.columnName === "__checkbox__" ? (
|
|
||||||
renderCheckboxHeader()
|
|
||||||
) : (
|
|
||||||
<div className="flex items-center space-x-1">
|
|
||||||
<span className="text-sm">{columnLabels[column.columnName] || column.displayName}</span>
|
|
||||||
{column.sortable && (
|
|
||||||
<div className="flex flex-col">
|
|
||||||
{sortColumn === column.columnName ? (
|
|
||||||
sortDirection === "asc" ? (
|
|
||||||
<ArrowUp className="h-3 w-3" />
|
|
||||||
) : (
|
|
||||||
<ArrowDown className="h-3 w-3" />
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<ArrowUpDown className="h-3 w-3 text-gray-400" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</th>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{data.length === 0 ? (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={columnsByPosition.rightFixed.length} className="py-8 text-center text-gray-500">
|
|
||||||
{columnsByPosition.leftFixed.length === 0 && columnsByPosition.normal.length === 0
|
|
||||||
? "데이터가 없습니다"
|
|
||||||
: ""}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
) : (
|
|
||||||
data.map((row, index) => (
|
|
||||||
<tr
|
|
||||||
key={`fixed-right-row-${index}`}
|
|
||||||
className={cn(
|
|
||||||
"h-12 cursor-pointer border-b leading-none",
|
|
||||||
tableConfig.tableStyle?.hoverEffect && "hover:bg-gray-50",
|
|
||||||
tableConfig.tableStyle?.alternateRows && index % 2 === 1 && "bg-gray-50/50",
|
|
||||||
)}
|
|
||||||
style={{ minHeight: "48px", height: "48px", lineHeight: "1" }}
|
|
||||||
onClick={() => handleRowClick(row)}
|
|
||||||
>
|
|
||||||
{columnsByPosition.rightFixed.map((column) => (
|
|
||||||
<td
|
|
||||||
key={`fixed-right-cell-${column.columnName}`}
|
|
||||||
className={cn(
|
|
||||||
"h-12 px-4 py-3 align-middle text-sm whitespace-nowrap",
|
|
||||||
`text-${column.align}`,
|
|
||||||
)}
|
|
||||||
style={{ minHeight: "48px", height: "48px", verticalAlign: "middle" }}
|
|
||||||
>
|
|
||||||
{column.columnName === "__checkbox__"
|
|
||||||
? renderCheckboxCell(row, index)
|
|
||||||
: formatCellValue(row[column.columnName], column.format, column.columnName) || "\u00A0"}
|
|
||||||
</td>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
// 기존 테이블 (가로 스크롤이 필요 없는 경우)
|
// 기존 테이블 (가로 스크롤이 필요 없는 경우)
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader className={tableConfig.stickyHeader ? "sticky top-0 z-10 bg-white" : ""}>
|
<TableHeader className={tableConfig.stickyHeader ? "sticky top-0 z-10 bg-white" : ""}>
|
||||||
<TableRow style={{ minHeight: "48px !important", height: "48px !important", lineHeight: "1" }}>
|
<TableRow style={{ minHeight: "40px !important", height: "40px !important", lineHeight: "1" }}>
|
||||||
{visibleColumns.map((column) => (
|
{visibleColumns.map((column) => (
|
||||||
<TableHead
|
<TableHead
|
||||||
key={column.columnName}
|
key={column.columnName}
|
||||||
style={{
|
style={{
|
||||||
width: column.width ? `${column.width}px` : undefined,
|
width: column.width ? `${column.width}px` : undefined,
|
||||||
minHeight: "48px !important",
|
minHeight: "40px !important",
|
||||||
height: "48px !important",
|
height: "40px !important",
|
||||||
verticalAlign: "middle",
|
verticalAlign: "middle",
|
||||||
lineHeight: "1",
|
lineHeight: "1",
|
||||||
boxSizing: "border-box",
|
boxSizing: "border-box",
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
column.columnName === "__checkbox__"
|
column.columnName === "__checkbox__"
|
||||||
? "h-12 text-center align-middle"
|
? "h-10 text-center align-middle"
|
||||||
: "h-12 cursor-pointer align-middle whitespace-nowrap select-none",
|
: "h-10 cursor-pointer align-middle whitespace-nowrap select-none",
|
||||||
`text-${column.align}`,
|
`text-${column.align}`,
|
||||||
column.sortable && "hover:bg-gray-50",
|
column.sortable && "hover:bg-gray-50",
|
||||||
)}
|
)}
|
||||||
|
|
@ -1286,18 +1021,18 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
<TableRow
|
<TableRow
|
||||||
key={index}
|
key={index}
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-12 cursor-pointer leading-none",
|
"h-10 cursor-pointer leading-none",
|
||||||
tableConfig.tableStyle?.hoverEffect && "hover:bg-gray-50",
|
tableConfig.tableStyle?.hoverEffect && "hover:bg-gray-50",
|
||||||
tableConfig.tableStyle?.alternateRows && index % 2 === 1 && "bg-gray-50/50",
|
tableConfig.tableStyle?.alternateRows && index % 2 === 1 && "bg-gray-50/50",
|
||||||
)}
|
)}
|
||||||
style={{ minHeight: "48px", height: "48px", lineHeight: "1" }}
|
style={{ minHeight: "40px", height: "40px", lineHeight: "1" }}
|
||||||
onClick={() => handleRowClick(row)}
|
onClick={() => handleRowClick(row)}
|
||||||
>
|
>
|
||||||
{visibleColumns.map((column) => (
|
{visibleColumns.map((column) => (
|
||||||
<TableCell
|
<TableCell
|
||||||
key={column.columnName}
|
key={column.columnName}
|
||||||
className={cn("h-12 align-middle whitespace-nowrap", `text-${column.align}`)}
|
className={cn("h-10 align-middle whitespace-nowrap", `text-${column.align}`)}
|
||||||
style={{ minHeight: "48px", height: "48px", verticalAlign: "middle" }}
|
style={{ minHeight: "40px", height: "40px", verticalAlign: "middle" }}
|
||||||
>
|
>
|
||||||
{column.columnName === "__checkbox__"
|
{column.columnName === "__checkbox__"
|
||||||
? renderCheckboxCell(row, index)
|
? renderCheckboxCell(row, index)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue