feat(modal-repeater-table): 체크박스 기반 일괄 삭제 기능 추가

- RepeaterTable: 체크박스 컬럼 추가 (전체 선택/개별 선택 지원)
- RepeaterTable: 선택된 행 시각적 피드백 (bg-blue-50)
- RepeaterTable: 기존 개별 삭제 버튼 컬럼 제거
- ModalRepeaterTableComponent: selectedRows 상태 및 handleBulkDelete 함수 추가
- ModalRepeaterTableComponent: "선택 삭제" 버튼 UI 추가
- RepeatScreenModalConfigPanel: 행 번호 컬럼 선택에서 빈 값 필터링
This commit is contained in:
SeongHyun Kim
2025-12-16 11:39:30 +09:00
parent 4cff9e4cec
commit 56608001ff
3 changed files with 102 additions and 33 deletions

View File

@@ -3,9 +3,9 @@
import React, { useState, useEffect } from "react";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Button } from "@/components/ui/button";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Trash2, ChevronDown, Check } from "lucide-react";
import { ChevronDown, Check } from "lucide-react";
import { Checkbox } from "@/components/ui/checkbox";
import { RepeaterColumnConfig } from "./types";
import { cn } from "@/lib/utils";
@@ -18,6 +18,9 @@ interface RepeaterTableProps {
// 동적 데이터 소스 관련
activeDataSources?: Record<string, string>; // 컬럼별 현재 활성화된 데이터 소스 ID
onDataSourceChange?: (columnField: string, optionId: string) => void; // 데이터 소스 변경 콜백
// 체크박스 선택 관련
selectedRows: Set<number>; // 선택된 행 인덱스
onSelectionChange: (selectedRows: Set<number>) => void; // 선택 변경 콜백
}
export function RepeaterTable({
@@ -28,6 +31,8 @@ export function RepeaterTable({
onRowDelete,
activeDataSources = {},
onDataSourceChange,
selectedRows,
onSelectionChange,
}: RepeaterTableProps) {
const [editingCell, setEditingCell] = useState<{
rowIndex: number;
@@ -112,6 +117,33 @@ export function RepeaterTable({
onRowChange(rowIndex, newRow);
};
// 전체 선택 체크박스 핸들러
const handleSelectAll = (checked: boolean) => {
if (checked) {
// 모든 행 선택
const allIndices = new Set(data.map((_, index) => index));
onSelectionChange(allIndices);
} else {
// 전체 해제
onSelectionChange(new Set());
}
};
// 개별 행 선택 핸들러
const handleRowSelect = (rowIndex: number, checked: boolean) => {
const newSelection = new Set(selectedRows);
if (checked) {
newSelection.add(rowIndex);
} else {
newSelection.delete(rowIndex);
}
onSelectionChange(newSelection);
};
// 전체 선택 상태 계산
const isAllSelected = data.length > 0 && selectedRows.size === data.length;
const isIndeterminate = selectedRows.size > 0 && selectedRows.size < data.length;
const renderCell = (
row: any,
column: RepeaterColumnConfig,
@@ -215,8 +247,17 @@ export function RepeaterTable({
<table className="w-full text-xs border-collapse">
<thead className="bg-gray-50 sticky top-0 z-10">
<tr>
<th className="px-3 py-2 text-left font-medium text-gray-700 border-b border-r border-gray-200 w-12">
#
<th className="px-3 py-2 text-center font-medium text-gray-700 border-b border-r border-gray-200 w-10">
<Checkbox
checked={isAllSelected}
// @ts-ignore - indeterminate는 HTML 속성
data-indeterminate={isIndeterminate}
onCheckedChange={handleSelectAll}
className={cn(
"border-gray-400",
isIndeterminate && "data-[state=checked]:bg-primary"
)}
/>
</th>
{columns.map((col) => {
const hasDynamicSource = col.dynamicDataSource?.enabled && col.dynamicDataSource.options.length > 0;
@@ -303,16 +344,13 @@ export function RepeaterTable({
</th>
);
})}
<th className="px-3 py-2 text-left font-medium text-gray-700 border-b border-r border-gray-200 w-20">
</th>
</tr>
</thead>
<tbody className="bg-white">
{data.length === 0 ? (
<tr>
<td
colSpan={columns.length + 2}
colSpan={columns.length + 1}
className="px-4 py-8 text-center text-gray-500 border-b border-gray-200"
>
@@ -320,25 +358,25 @@ export function RepeaterTable({
</tr>
) : (
data.map((row, rowIndex) => (
<tr key={rowIndex} className="hover:bg-blue-50/50 transition-colors">
<td className="px-3 py-1 text-center text-gray-600 border-b border-r border-gray-200">
{rowIndex + 1}
<tr
key={rowIndex}
className={cn(
"hover:bg-blue-50/50 transition-colors",
selectedRows.has(rowIndex) && "bg-blue-50"
)}
>
<td className="px-3 py-1 text-center border-b border-r border-gray-200">
<Checkbox
checked={selectedRows.has(rowIndex)}
onCheckedChange={(checked) => handleRowSelect(rowIndex, !!checked)}
className="border-gray-400"
/>
</td>
{columns.map((col) => (
<td key={col.field} className="px-1 py-1 border-b border-r border-gray-200">
{renderCell(row, col, rowIndex)}
</td>
))}
<td className="px-3 py-1 text-center border-b border-r border-gray-200">
<Button
variant="ghost"
size="sm"
onClick={() => onRowDelete(rowIndex)}
className="h-7 w-7 p-0 text-red-500 hover:text-red-700 hover:bg-red-50"
>
<Trash2 className="h-4 w-4" />
</Button>
</td>
</tr>
))
)}