검색필터 수정사항
This commit is contained in:
parent
c557fc5d56
commit
f15c1fa114
|
|
@ -25,7 +25,7 @@ export const ResponsiveDesignerContainer: React.FC<ResponsiveDesignerContainerPr
|
|||
onScaleChange,
|
||||
}) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [viewMode, setViewMode] = useState<DesignerViewMode>("fit");
|
||||
const [viewMode, setViewMode] = useState<DesignerViewMode>("original");
|
||||
const [customScale, setCustomScale] = useState(1);
|
||||
const containerSize = useContainerSize(containerRef);
|
||||
|
||||
|
|
|
|||
|
|
@ -317,7 +317,7 @@ export const AdvancedSearchFilters: React.FC<AdvancedSearchFiltersProps> = ({
|
|||
}).length;
|
||||
|
||||
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">
|
||||
<Search className="h-3 w-3" />
|
||||
|
|
@ -339,7 +339,7 @@ export const AdvancedSearchFilters: React.FC<AdvancedSearchFiltersProps> = ({
|
|||
};
|
||||
|
||||
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>
|
||||
{renderFilter(filter)}
|
||||
</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 { AdvancedSearchFilters } from "@/components/screen/filters/AdvancedSearchFilters";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { SingleTableWithSticky } from "./SingleTableWithSticky";
|
||||
|
||||
export interface TableListComponentProps {
|
||||
component: any;
|
||||
|
|
@ -113,39 +114,67 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
maxBatchSize: 5,
|
||||
});
|
||||
|
||||
// 높이 계산 함수
|
||||
const calculateOptimalHeight = () => {
|
||||
// 50개 이상일 때는 20개 기준으로 높이 고정
|
||||
const displayPageSize = localPageSize >= 50 ? 20 : localPageSize;
|
||||
const headerHeight = 48; // 테이블 헤더
|
||||
const rowHeight = 40; // 각 행 높이 (normal)
|
||||
const searchHeight = tableConfig.filter?.enabled ? 48 : 0; // 검색 영역
|
||||
const footerHeight = tableConfig.showFooter ? 56 : 0; // 페이지네이션
|
||||
const padding = 8; // 여백
|
||||
// 높이 계산 함수 (메모이제이션)
|
||||
const optimalHeight = useMemo(() => {
|
||||
// 50개 이상일 때는 20개 기준으로 높이 고정하고 스크롤 생성
|
||||
// 50개 미만일 때는 실제 데이터 개수에 맞춰서 스크롤 없이 표시
|
||||
const actualDataCount = Math.min(data.length, localPageSize);
|
||||
const displayPageSize = localPageSize >= 50 ? 20 : Math.max(actualDataCount, 5);
|
||||
|
||||
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 = {
|
||||
width: "100%",
|
||||
height:
|
||||
tableConfig.height === "fixed"
|
||||
? `${tableConfig.fixedHeight || calculateOptimalHeight()}px`
|
||||
: tableConfig.height === "auto"
|
||||
? `${calculateOptimalHeight()}px`
|
||||
: "100%",
|
||||
height: `${optimalHeight}px`, // 20개 데이터를 모두 보여주는 높이
|
||||
minHeight: `${optimalHeight}px`, // 최소 높이 보장
|
||||
...component.style,
|
||||
...style,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
boxSizing: "border-box", // 패딩/보더 포함한 크기 계산
|
||||
};
|
||||
|
||||
// 디자인 모드 스타일
|
||||
if (isDesignMode) {
|
||||
componentStyle.border = "1px dashed #cbd5e1";
|
||||
componentStyle.borderColor = isSelected ? "#3b82f6" : "#cbd5e1";
|
||||
componentStyle.minHeight = "200px";
|
||||
// minHeight 제거 - 실제 데이터에 맞는 높이 사용
|
||||
}
|
||||
|
||||
// 컬럼 라벨 정보 가져오기
|
||||
|
|
@ -637,28 +666,8 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
return columns;
|
||||
}, [displayColumns, tableConfig.columns, tableConfig.checkbox]);
|
||||
|
||||
// 컬럼을 고정 위치별로 분류
|
||||
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]);
|
||||
// columnsByPosition은 SingleTableWithSticky에서 사용하지 않으므로 제거
|
||||
// 기존 테이블에서만 필요한 경우 다시 추가 가능
|
||||
|
||||
// 가로 스크롤이 필요한지 계산
|
||||
const needsHorizontalScroll = useMemo(() => {
|
||||
|
|
@ -893,7 +902,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
{/* 고급 검색 필터 - 항상 표시 (컬럼 정보 기반 자동 생성) */}
|
||||
{tableConfig.filter?.enabled && visibleColumns && visibleColumns.length > 0 && (
|
||||
<>
|
||||
<Separator className="my-2" />
|
||||
<Separator className="my-1" />
|
||||
<AdvancedSearchFilters
|
||||
filters={tableConfig.filter?.filters || []} // 설정된 필터 사용, 없으면 자동 생성
|
||||
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 ? (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<div className="text-center">
|
||||
|
|
@ -934,317 +943,43 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
</div>
|
||||
</div>
|
||||
) : needsHorizontalScroll ? (
|
||||
// 가로 스크롤이 필요한 경우 - 고정 컬럼 지원 테이블
|
||||
<div className="relative flex h-full">
|
||||
{/* 왼쪽 고정 컬럼 */}
|
||||
{columnsByPosition.leftFixed.length > 0 && (
|
||||
<div className="flex-shrink-0 border-r 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.leftFixed.map((column) => (
|
||||
<th
|
||||
key={`fixed-left-${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.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>
|
||||
// 가로 스크롤이 필요한 경우 - 단일 테이블에서 sticky 컬럼 사용
|
||||
<SingleTableWithSticky
|
||||
visibleColumns={visibleColumns}
|
||||
data={data}
|
||||
columnLabels={columnLabels}
|
||||
sortColumn={sortColumn}
|
||||
sortDirection={sortDirection}
|
||||
tableConfig={tableConfig}
|
||||
isDesignMode={isDesignMode}
|
||||
isAllSelected={isAllSelected}
|
||||
handleSort={handleSort}
|
||||
handleSelectAll={handleSelectAll}
|
||||
handleRowClick={handleRowClick}
|
||||
renderCheckboxCell={renderCheckboxCell}
|
||||
formatCellValue={formatCellValue}
|
||||
getColumnWidth={getColumnWidth}
|
||||
/>
|
||||
) : (
|
||||
// 기존 테이블 (가로 스크롤이 필요 없는 경우)
|
||||
<Table>
|
||||
<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) => (
|
||||
<TableHead
|
||||
key={column.columnName}
|
||||
style={{
|
||||
width: column.width ? `${column.width}px` : undefined,
|
||||
minHeight: "48px !important",
|
||||
height: "48px !important",
|
||||
minHeight: "40px !important",
|
||||
height: "40px !important",
|
||||
verticalAlign: "middle",
|
||||
lineHeight: "1",
|
||||
boxSizing: "border-box",
|
||||
}}
|
||||
className={cn(
|
||||
column.columnName === "__checkbox__"
|
||||
? "h-12 text-center align-middle"
|
||||
: "h-12 cursor-pointer align-middle whitespace-nowrap select-none",
|
||||
? "h-10 text-center align-middle"
|
||||
: "h-10 cursor-pointer align-middle whitespace-nowrap select-none",
|
||||
`text-${column.align}`,
|
||||
column.sortable && "hover:bg-gray-50",
|
||||
)}
|
||||
|
|
@ -1286,18 +1021,18 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
<TableRow
|
||||
key={index}
|
||||
className={cn(
|
||||
"h-12 cursor-pointer leading-none",
|
||||
"h-10 cursor-pointer 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" }}
|
||||
style={{ minHeight: "40px", height: "40px", lineHeight: "1" }}
|
||||
onClick={() => handleRowClick(row)}
|
||||
>
|
||||
{visibleColumns.map((column) => (
|
||||
<TableCell
|
||||
key={column.columnName}
|
||||
className={cn("h-12 align-middle whitespace-nowrap", `text-${column.align}`)}
|
||||
style={{ minHeight: "48px", height: "48px", verticalAlign: "middle" }}
|
||||
className={cn("h-10 align-middle whitespace-nowrap", `text-${column.align}`)}
|
||||
style={{ minHeight: "40px", height: "40px", verticalAlign: "middle" }}
|
||||
>
|
||||
{column.columnName === "__checkbox__"
|
||||
? renderCheckboxCell(row, index)
|
||||
|
|
|
|||
Loading…
Reference in New Issue