; Please enter a commit message to explain why this merge is necessary,
; especially if it merges an updated upstream into a topic branch.
;
; Lines starting with ';' will be ignored, and an empty message aborts
; the commit.
This commit is contained in:
leeheejin 2025-12-10 15:53:11 +09:00
commit 6707e2afd2
1 changed files with 51 additions and 36 deletions

View File

@ -130,7 +130,7 @@ export function FlowWidget({
const [stepData, setStepData] = useState<any[]>([]);
const [stepDataColumns, setStepDataColumns] = useState<string[]>([]);
const [stepDataLoading, setStepDataLoading] = useState(false);
const [selectedRows, setSelectedRows] = useState<Set<number>>(new Set());
const [selectedRows, setSelectedRows] = useState<Set<string>>(new Set()); // Primary Key 값으로 선택 관리
const [columnLabels, setColumnLabels] = useState<Record<string, string>>({}); // 컬럼명 -> 라벨 매핑
// 🆕 검색 필터 관련 상태
@ -753,25 +753,35 @@ export function FlowWidget({
}
};
// 체크박스 토글
const toggleRowSelection = (rowIndex: number) => {
// Primary Key 컬럼명 (플로우 정의에서 가져오거나 기본값 id)
const primaryKeyColumn = flowData?.primaryKey || "id";
// 행의 Primary Key 값 가져오기
const getRowKey = useCallback((row: any): string => {
const keyValue = row[primaryKeyColumn] || row.id;
return String(keyValue);
}, [primaryKeyColumn]);
// 체크박스 토글 (Primary Key 기반)
const toggleRowSelection = (row: any) => {
// 프리뷰 모드에서는 행 선택 차단
if (isPreviewMode) {
return;
}
const rowKey = getRowKey(row);
const newSelected = new Set(selectedRows);
if (newSelected.has(rowIndex)) {
newSelected.delete(rowIndex);
if (newSelected.has(rowKey)) {
newSelected.delete(rowKey);
} else {
newSelected.add(rowIndex);
newSelected.add(rowKey);
}
setSelectedRows(newSelected);
// 선택된 데이터를 상위로 전달
const selectedData = Array.from(newSelected).map((index) => stepData[index]);
// 선택된 데이터를 상위로 전달 (stepData에서 선택된 행들 찾기)
const selectedData = stepData.filter((r) => newSelected.has(getRowKey(r)));
console.log("🌊 FlowWidget - 체크박스 토글, 상위로 전달:", {
rowIndex,
rowKey,
newSelectedSize: newSelected.size,
selectedData,
selectedStepId,
@ -780,18 +790,18 @@ export function FlowWidget({
onSelectedDataChange?.(selectedData, selectedStepId);
};
// 전체 선택/해제
// 전체 선택/해제 (Primary Key 기반)
const toggleAllRows = () => {
let newSelected: Set<number>;
let newSelected: Set<string>;
if (selectedRows.size === stepData.length) {
newSelected = new Set();
} else {
newSelected = new Set(stepData.map((_, index) => index));
newSelected = new Set(stepData.map((row) => getRowKey(row)));
}
setSelectedRows(newSelected);
// 선택된 데이터를 상위로 전달
const selectedData = Array.from(newSelected).map((index) => stepData[index]);
const selectedData = stepData.filter((row) => newSelected.has(getRowKey(row)));
onSelectedDataChange?.(selectedData, selectedStepId);
};
@ -951,36 +961,41 @@ export function FlowWidget({
return formatValue(value);
}, []);
// 🆕 전체 선택 핸들러
// 🆕 전체 선택 핸들러 (Primary Key 기반)
const handleSelectAll = useCallback((checked: boolean) => {
if (checked) {
const allIndices = new Set(sortedDisplayData.map((_, idx) => idx));
setSelectedRows(allIndices);
const allKeys = new Set(sortedDisplayData.map((row) => getRowKey(row)));
setSelectedRows(allKeys);
// 선택된 데이터를 상위로 전달
onSelectedDataChange?.(sortedDisplayData, selectedStepId);
} else {
setSelectedRows(new Set());
onSelectedDataChange?.([], selectedStepId);
}
}, [sortedDisplayData]);
}, [sortedDisplayData, getRowKey, onSelectedDataChange, selectedStepId]);
// 🆕 행 클릭 핸들러
const handleRowClick = useCallback((row: any) => {
// 필요 시 행 클릭 로직 추가
}, []);
// 🆕 체크박스 셀 렌더링
const renderCheckboxCell = useCallback((row: any, index: number) => {
// 🆕 체크박스 셀 렌더링 (Primary Key 기반)
// index 파라미터는 SingleTableWithSticky 인터페이스 호환을 위해 유지하지만 사용하지 않음
const renderCheckboxCell = useCallback((row: any, _index: number) => {
const rowKey = getRowKey(row);
return (
<Checkbox
checked={selectedRows.has(index)}
onCheckedChange={() => toggleRowSelection(index)}
checked={selectedRows.has(rowKey)}
onCheckedChange={() => toggleRowSelection(row)}
/>
);
}, [selectedRows, toggleRowSelection]);
}, [selectedRows, toggleRowSelection, getRowKey]);
// 🆕 Excel 내보내기
// 🆕 Excel 내보내기 (Primary Key 기반)
const exportToExcel = useCallback(() => {
try {
const exportData = selectedRows.size > 0
? sortedDisplayData.filter((_, idx) => selectedRows.has(idx))
? sortedDisplayData.filter((row) => selectedRows.has(getRowKey(row)))
: sortedDisplayData;
if (exportData.length === 0) {
@ -1010,13 +1025,13 @@ export function FlowWidget({
console.error("Excel 내보내기 오류:", error);
toast.error("Excel 내보내기에 실패했습니다.");
}
}, [sortedDisplayData, selectedRows, stepDataColumns, columnLabels, flowName]);
}, [sortedDisplayData, selectedRows, stepDataColumns, columnLabels, flowName, getRowKey]);
// 🆕 PDF 내보내기 (html2canvas 사용으로 한글 지원)
const exportToPdf = useCallback(async () => {
try {
const exportData = selectedRows.size > 0
? sortedDisplayData.filter((_, idx) => selectedRows.has(idx))
? sortedDisplayData.filter((row) => selectedRows.has(getRowKey(row)))
: sortedDisplayData;
if (exportData.length === 0) {
@ -1175,13 +1190,13 @@ export function FlowWidget({
console.error("PDF 내보내기 오류:", error);
toast.error("PDF 내보내기에 실패했습니다.", { id: "pdf-export" });
}
}, [sortedDisplayData, selectedRows, stepDataColumns, columnLabels, flowName]);
}, [sortedDisplayData, selectedRows, stepDataColumns, columnLabels, flowName, getRowKey]);
// 🆕 복사 기능
// 🆕 복사 기능 (Primary Key 기반)
const handleCopy = useCallback(() => {
try {
const copyData = selectedRows.size > 0
? sortedDisplayData.filter((_, idx) => selectedRows.has(idx))
? sortedDisplayData.filter((row) => selectedRows.has(getRowKey(row)))
: [];
if (copyData.length === 0) {
@ -1203,7 +1218,7 @@ export function FlowWidget({
console.error("복사 오류:", error);
toast.error("복사에 실패했습니다.");
}
}, [sortedDisplayData, selectedRows, stepDataColumns, columnLabels]);
}, [sortedDisplayData, selectedRows, stepDataColumns, columnLabels, getRowKey]);
// 🆕 통합 검색 실행
const executeGlobalSearch = useCallback((term: string) => {
@ -1828,15 +1843,15 @@ export function FlowWidget({
<div
key={actualIndex}
className={`bg-card rounded-md border p-3 transition-colors ${
selectedRows.has(actualIndex) ? "bg-primary/5 border-primary/30" : ""
selectedRows.has(getRowKey(row)) ? "bg-primary/5 border-primary/30" : ""
}`}
>
{allowDataMove && (
<div className="mb-2 flex items-center justify-between border-b pb-2">
<span className="text-muted-foreground text-xs font-medium"></span>
<Checkbox
checked={selectedRows.has(actualIndex)}
onCheckedChange={() => toggleRowSelection(actualIndex)}
checked={selectedRows.has(getRowKey(row))}
onCheckedChange={() => toggleRowSelection(row)}
/>
</div>
)}
@ -1919,13 +1934,13 @@ export function FlowWidget({
return (
<TableRow
key={`${group.groupKey}-${itemIndex}`}
className={`h-16 transition-colors hover:bg-muted/50 ${selectedRows.has(actualIndex) ? "bg-primary/5" : ""}`}
className={`h-16 transition-colors hover:bg-muted/50 ${selectedRows.has(getRowKey(row)) ? "bg-primary/5" : ""}`}
>
{allowDataMove && (
<TableCell className="bg-background sticky left-0 z-10 border-b px-6 py-3 text-center">
<Checkbox
checked={selectedRows.has(actualIndex)}
onCheckedChange={() => toggleRowSelection(actualIndex)}
checked={selectedRows.has(getRowKey(row))}
onCheckedChange={() => toggleRowSelection(row)}
/>
</TableCell>
)}