Merge pull request 'lhj' (#372) from lhj into main
Reviewed-on: http://39.117.244.52:3000/kjs/ERP-node/pulls/372
This commit is contained in:
commit
147d187901
|
|
@ -1674,7 +1674,11 @@ export const PivotGridComponent: React.FC<PivotGridProps> = ({
|
||||||
className="flex-1 overflow-auto focus:outline-none"
|
className="flex-1 overflow-auto focus:outline-none"
|
||||||
style={{
|
style={{
|
||||||
maxHeight: enableVirtualScroll && containerHeight > 0 ? containerHeight : undefined,
|
maxHeight: enableVirtualScroll && containerHeight > 0 ? containerHeight : undefined,
|
||||||
minHeight: 100 // 최소 높이 보장 - 블라인드 효과 방지
|
// 최소 200px 보장 + 데이터에 맞게 조정 (최대 400px)
|
||||||
|
minHeight: Math.max(
|
||||||
|
200, // 절대 최소값 - 블라인드 효과 방지
|
||||||
|
Math.min(400, (sortedFlatRows.length + 3) * ROW_HEIGHT + 50)
|
||||||
|
)
|
||||||
}}
|
}}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ import {
|
||||||
Lock,
|
Lock,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import * as XLSX from "xlsx";
|
import * as XLSX from "xlsx";
|
||||||
import { FileText, ChevronRightIcon } from "lucide-react";
|
import { FileText, ChevronRightIcon, Search } from "lucide-react";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
@ -455,6 +455,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
|
|
||||||
// 🆕 컬럼 헤더 필터 상태 (상단에서 선언)
|
// 🆕 컬럼 헤더 필터 상태 (상단에서 선언)
|
||||||
const [headerFilters, setHeaderFilters] = useState<Record<string, Set<string>>>({});
|
const [headerFilters, setHeaderFilters] = useState<Record<string, Set<string>>>({});
|
||||||
|
const [headerLikeFilters, setHeaderLikeFilters] = useState<Record<string, string>>({}); // LIKE 검색용
|
||||||
const [openFilterColumn, setOpenFilterColumn] = useState<string | null>(null);
|
const [openFilterColumn, setOpenFilterColumn] = useState<string | null>(null);
|
||||||
|
|
||||||
// 🆕 Filter Builder (고급 필터) 관련 상태 - filteredData보다 먼저 정의해야 함
|
// 🆕 Filter Builder (고급 필터) 관련 상태 - filteredData보다 먼저 정의해야 함
|
||||||
|
|
@ -488,6 +489,22 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2-1. 🆕 LIKE 검색 필터 적용
|
||||||
|
if (Object.keys(headerLikeFilters).length > 0) {
|
||||||
|
result = result.filter((row) => {
|
||||||
|
return Object.entries(headerLikeFilters).every(([columnName, searchText]) => {
|
||||||
|
if (!searchText || searchText.trim() === "") return true;
|
||||||
|
|
||||||
|
// 여러 가능한 컬럼명 시도
|
||||||
|
const cellValue = row[columnName] ?? row[columnName.toLowerCase()] ?? row[columnName.toUpperCase()];
|
||||||
|
const cellStr = cellValue !== null && cellValue !== undefined ? String(cellValue).toLowerCase() : "";
|
||||||
|
|
||||||
|
// LIKE 검색 (대소문자 무시)
|
||||||
|
return cellStr.includes(searchText.toLowerCase());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 3. 🆕 Filter Builder 적용
|
// 3. 🆕 Filter Builder 적용
|
||||||
if (filterGroups.length > 0) {
|
if (filterGroups.length > 0) {
|
||||||
result = result.filter((row) => {
|
result = result.filter((row) => {
|
||||||
|
|
@ -541,7 +558,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}, [data, splitPanelPosition, splitPanelContext?.addedItemIds, headerFilters, filterGroups]);
|
}, [data, splitPanelPosition, splitPanelContext?.addedItemIds, headerFilters, headerLikeFilters, filterGroups]);
|
||||||
|
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [totalPages, setTotalPages] = useState(0);
|
const [totalPages, setTotalPages] = useState(0);
|
||||||
|
|
@ -2935,6 +2952,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
headerFilters: Object.fromEntries(
|
headerFilters: Object.fromEntries(
|
||||||
Object.entries(headerFilters).map(([key, set]) => [key, Array.from(set as Set<string>)]),
|
Object.entries(headerFilters).map(([key, set]) => [key, Array.from(set as Set<string>)]),
|
||||||
),
|
),
|
||||||
|
headerLikeFilters, // LIKE 검색 필터 저장
|
||||||
pageSize: localPageSize,
|
pageSize: localPageSize,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
};
|
};
|
||||||
|
|
@ -2955,6 +2973,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
frozenColumnCount,
|
frozenColumnCount,
|
||||||
showGridLines,
|
showGridLines,
|
||||||
headerFilters,
|
headerFilters,
|
||||||
|
headerLikeFilters,
|
||||||
localPageSize,
|
localPageSize,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -2991,6 +3010,9 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
});
|
});
|
||||||
setHeaderFilters(filters);
|
setHeaderFilters(filters);
|
||||||
}
|
}
|
||||||
|
if (state.headerLikeFilters) {
|
||||||
|
setHeaderLikeFilters(state.headerLikeFilters);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ 테이블 상태 복원 실패:", error);
|
console.error("❌ 테이블 상태 복원 실패:", error);
|
||||||
}
|
}
|
||||||
|
|
@ -5737,7 +5759,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
"hover:bg-primary/20 ml-1 rounded p-0.5 transition-colors",
|
"hover:bg-primary/20 ml-1 rounded p-0.5 transition-colors",
|
||||||
headerFilters[column.columnName]?.size > 0 && "text-primary bg-primary/10",
|
(headerFilters[column.columnName]?.size > 0 || headerLikeFilters[column.columnName]) && "text-primary bg-primary/10",
|
||||||
)}
|
)}
|
||||||
title="필터"
|
title="필터"
|
||||||
>
|
>
|
||||||
|
|
@ -5745,7 +5767,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
</button>
|
</button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent
|
<PopoverContent
|
||||||
className="w-48 p-2"
|
className="w-56 p-2"
|
||||||
align="start"
|
align="start"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
|
|
@ -5754,16 +5776,42 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
<span className="text-xs font-medium">
|
<span className="text-xs font-medium">
|
||||||
필터: {columnLabels[column.columnName] || column.displayName}
|
필터: {columnLabels[column.columnName] || column.displayName}
|
||||||
</span>
|
</span>
|
||||||
{headerFilters[column.columnName]?.size > 0 && (
|
{(headerFilters[column.columnName]?.size > 0 || headerLikeFilters[column.columnName]) && (
|
||||||
<button
|
<button
|
||||||
onClick={() => clearHeaderFilter(column.columnName)}
|
onClick={() => {
|
||||||
|
clearHeaderFilter(column.columnName);
|
||||||
|
setHeaderLikeFilters((prev) => {
|
||||||
|
const newFilters = { ...prev };
|
||||||
|
delete newFilters[column.columnName];
|
||||||
|
return newFilters;
|
||||||
|
});
|
||||||
|
}}
|
||||||
className="text-destructive text-xs hover:underline"
|
className="text-destructive text-xs hover:underline"
|
||||||
>
|
>
|
||||||
초기화
|
초기화
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="max-h-48 space-y-1 overflow-y-auto">
|
{/* LIKE 검색 입력 필드 */}
|
||||||
|
<div className="relative">
|
||||||
|
<Search className="text-muted-foreground absolute left-2 top-1/2 h-3 w-3 -translate-y-1/2" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="검색어 입력 (포함)"
|
||||||
|
value={headerLikeFilters[column.columnName] || ""}
|
||||||
|
onChange={(e) => {
|
||||||
|
setHeaderLikeFilters((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[column.columnName]: e.target.value,
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
className="border-input bg-background placeholder:text-muted-foreground h-7 w-full rounded-md border pl-7 pr-2 text-xs focus:outline-none focus:ring-1 focus:ring-primary"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* 구분선 */}
|
||||||
|
<div className="text-muted-foreground border-t pt-2 text-[10px]">또는 값 선택:</div>
|
||||||
|
<div className="max-h-40 space-y-1 overflow-y-auto">
|
||||||
{columnUniqueValues[column.columnName]?.slice(0, 50).map((val) => {
|
{columnUniqueValues[column.columnName]?.slice(0, 50).map((val) => {
|
||||||
const isSelected = headerFilters[column.columnName]?.has(val);
|
const isSelected = headerFilters[column.columnName]?.has(val);
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue