From 56608001ff9a6c787802bd01f61b9307e895e22b Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Tue, 16 Dec 2025 11:39:30 +0900 Subject: [PATCH] =?UTF-8?q?feat(modal-repeater-table):=20=EC=B2=B4?= =?UTF-8?q?=ED=81=AC=EB=B0=95=EC=8A=A4=20=EA=B8=B0=EB=B0=98=20=EC=9D=BC?= =?UTF-8?q?=EA=B4=84=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RepeaterTable: 체크박스 컬럼 추가 (전체 선택/개별 선택 지원) - RepeaterTable: 선택된 행 시각적 피드백 (bg-blue-50) - RepeaterTable: 기존 개별 삭제 버튼 컬럼 제거 - ModalRepeaterTableComponent: selectedRows 상태 및 handleBulkDelete 함수 추가 - ModalRepeaterTableComponent: "선택 삭제" 버튼 UI 추가 - RepeatScreenModalConfigPanel: 행 번호 컬럼 선택에서 빈 값 필터링 --- .../ModalRepeaterTableComponent.tsx | 43 ++++++++-- .../modal-repeater-table/RepeaterTable.tsx | 80 ++++++++++++++----- .../RepeatScreenModalConfigPanel.tsx | 12 +-- 3 files changed, 102 insertions(+), 33 deletions(-) diff --git a/frontend/lib/registry/components/modal-repeater-table/ModalRepeaterTableComponent.tsx b/frontend/lib/registry/components/modal-repeater-table/ModalRepeaterTableComponent.tsx index e16c5d72..6177f647 100644 --- a/frontend/lib/registry/components/modal-repeater-table/ModalRepeaterTableComponent.tsx +++ b/frontend/lib/registry/components/modal-repeater-table/ModalRepeaterTableComponent.tsx @@ -328,6 +328,9 @@ export function ModalRepeaterTableComponent({ const companyCode = componentConfig?.companyCode || propCompanyCode; const [modalOpen, setModalOpen] = useState(false); + // 체크박스 선택 상태 + const [selectedRows, setSelectedRows] = useState>(new Set()); + // 🆕 납기일 일괄 적용 플래그 (딱 한 번만 실행) const [isDeliveryDateApplied, setIsDeliveryDateApplied] = useState(false); @@ -794,6 +797,18 @@ export function ModalRepeaterTableComponent({ handleChange(newData); }; + // 선택된 항목 일괄 삭제 핸들러 + const handleBulkDelete = () => { + if (selectedRows.size === 0) return; + + // 선택되지 않은 항목만 남김 + const newData = localValue.filter((_, index) => !selectedRows.has(index)); + + // 데이터 업데이트 및 선택 상태 초기화 + handleChange(newData); + setSelectedRows(new Set()); + }; + // 컬럼명 -> 라벨명 매핑 생성 (sourceColumnLabels 우선, 없으면 columns에서 가져옴) const columnLabels = columns.reduce((acc, col) => { // sourceColumnLabels에 정의된 라벨 우선 사용 @@ -807,14 +822,26 @@ export function ModalRepeaterTableComponent({
{localValue.length > 0 && `${localValue.length}개 항목`} + {selectedRows.size > 0 && ` (${selectedRows.size}개 선택됨)`} +
+
+ {selectedRows.size > 0 && ( + + )} +
-
{/* Repeater 테이블 */} @@ -826,6 +853,8 @@ export function ModalRepeaterTableComponent({ onRowDelete={handleRowDelete} activeDataSources={activeDataSources} onDataSourceChange={handleDataSourceChange} + selectedRows={selectedRows} + onSelectionChange={setSelectedRows} /> {/* 항목 선택 모달 */} diff --git a/frontend/lib/registry/components/modal-repeater-table/RepeaterTable.tsx b/frontend/lib/registry/components/modal-repeater-table/RepeaterTable.tsx index 56e9a321..1badecf9 100644 --- a/frontend/lib/registry/components/modal-repeater-table/RepeaterTable.tsx +++ b/frontend/lib/registry/components/modal-repeater-table/RepeaterTable.tsx @@ -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; // 컬럼별 현재 활성화된 데이터 소스 ID onDataSourceChange?: (columnField: string, optionId: string) => void; // 데이터 소스 변경 콜백 + // 체크박스 선택 관련 + selectedRows: Set; // 선택된 행 인덱스 + onSelectionChange: (selectedRows: Set) => 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({ - {columns.map((col) => { const hasDynamicSource = col.dynamicDataSource?.enabled && col.dynamicDataSource.options.length > 0; @@ -303,16 +344,13 @@ export function RepeaterTable({ ); })} - {data.length === 0 ? ( ) : ( data.map((row, rowIndex) => ( - - + {columns.map((col) => ( ))} - )) )} diff --git a/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalConfigPanel.tsx b/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalConfigPanel.tsx index 0c2edc4e..2a77f96a 100644 --- a/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalConfigPanel.tsx +++ b/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalConfigPanel.tsx @@ -1744,11 +1744,13 @@ function RowNumberingConfigSection({ - {tableColumns.map((col, index) => ( - - {col.label || col.field} - - ))} + {tableColumns + .filter((col) => col.field && col.field.trim() !== "") + .map((col, index) => ( + + {col.label || col.field} + + ))}

- # + + - 삭제 -
추가된 항목이 없습니다 @@ -320,25 +358,25 @@ export function RepeaterTable({
- {rowIndex + 1} +
+ handleRowSelect(rowIndex, !!checked)} + className="border-gray-400" + /> {renderCell(row, col, rowIndex)} - -