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