From b286bc3c637619896e8aa3a5115da58585a2339b Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Tue, 2 Dec 2025 14:50:00 +0900 Subject: [PATCH] =?UTF-8?q?feat(repeat-screen-modal):=20=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=B8=94=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20DB=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99=20(=EC=86=8C=ED=94=84=ED=8A=B8=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 삭제 버튼 클릭 시 _isDeleted 플래그 설정 (소프트 삭제) - 삭제된 행 시각적 표시 (취소선, 투명도) - 삭제 취소(복원) 기능 추가 - 저장 버튼 클릭 시 DELETE API 호출하여 DB 반영 - 삭제된 행 집계 계산에서 제외 - axios DELETE 요청 시 body 전달 방식 수정 --- .../RepeatScreenModalComponent.tsx | 130 ++++++++++++++---- 1 file changed, 106 insertions(+), 24 deletions(-) diff --git a/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalComponent.tsx b/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalComponent.tsx index 2484f1d7..3bbdf039 100644 --- a/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalComponent.tsx +++ b/frontend/lib/registry/components/repeat-screen-modal/RepeatScreenModalComponent.tsx @@ -481,7 +481,8 @@ export function RepeatScreenModalComponent({ // 각 카드의 집계 재계산 const updatedCards = groupedCardsData.map((card) => { const key = `${card._cardId}-${tableRowWithExternalSource.id}`; - const externalRows = extData[key] || []; + // 🆕 v3.7: 삭제된 행은 집계에서 제외 + const externalRows = (extData[key] || []).filter((row) => !row._isDeleted); // 집계 재계산 const newAggregations: Record = {}; @@ -671,8 +672,35 @@ export function RepeatScreenModalComponent({ inputType: c.inputType }))); - for (const row of dirtyRows) { - const { _rowId, _originalData, _isDirty, _isNew, ...allData } = row; + // 삭제할 행 (기존 데이터 중 _isDeleted가 true인 것) + const deletedRows = dirtyRows.filter((row) => row._isDeleted && row._originalData?.id); + // 저장할 행 (삭제되지 않은 것) + const rowsToSave = dirtyRows.filter((row) => !row._isDeleted); + + console.log("[RepeatScreenModal] 삭제 대상:", deletedRows.length, "건"); + console.log("[RepeatScreenModal] 저장 대상:", rowsToSave.length, "건"); + + // 🆕 v3.7: 삭제 처리 (배열 형태로 body에 전달) + for (const row of deletedRows) { + const deleteId = row._originalData.id; + console.log(`[RepeatScreenModal] DELETE 요청: /table-management/tables/${targetTable}/delete`, [{ id: deleteId }]); + savePromises.push( + apiClient.request({ + method: "DELETE", + url: `/table-management/tables/${targetTable}/delete`, + data: [{ id: deleteId }], + }).then((res) => { + console.log("[RepeatScreenModal] DELETE 응답:", res.data); + return { type: "delete", id: deleteId }; + }).catch((err) => { + console.error("[RepeatScreenModal] DELETE 실패:", err.response?.data || err.message); + throw err; + }) + ); + } + + for (const row of rowsToSave) { + const { _rowId, _originalData, _isDirty, _isNew, _isDeleted, ...allData } = row; // 허용된 필드만 필터링 const dataToSave: Record = {}; @@ -724,21 +752,30 @@ export function RepeatScreenModalComponent({ try { await Promise.all(savePromises); - // 저장 후 해당 키의 dirty 플래그만 초기화 + // 저장 후: 삭제된 행은 제거, 나머지는 dirty 플래그 초기화 setExternalTableData((prev) => { const updated = { ...prev }; if (updated[key]) { - updated[key] = updated[key].map((row) => ({ - ...row, - _isDirty: false, - _isNew: false, - _originalData: { ...row, _rowId: undefined, _originalData: undefined, _isDirty: undefined, _isNew: undefined }, - })); + // 삭제된 행은 완전히 제거 + updated[key] = updated[key] + .filter((row) => !row._isDeleted) + .map((row) => ({ + ...row, + _isDirty: false, + _isNew: false, + _originalData: { ...row, _rowId: undefined, _originalData: undefined, _isDirty: undefined, _isNew: undefined, _isDeleted: undefined }, + })); } return updated; }); - return { success: true, message: `${dirtyRows.length}건 저장 완료`, savedCount: dirtyRows.length, savedIds }; + const savedCount = rowsToSave.length; + const deletedCount = deletedRows.length; + const message = deletedCount > 0 + ? `${savedCount}건 저장, ${deletedCount}건 삭제 완료` + : `${savedCount}건 저장 완료`; + + return { success: true, message, savedCount, deletedCount, savedIds }; } catch (error: any) { console.error("[RepeatScreenModal] 테이블 영역 저장 실패:", error); return { success: false, message: error.message || "저장 중 오류가 발생했습니다." }; @@ -774,16 +811,20 @@ export function RepeatScreenModalComponent({ } }; - // 🆕 v3.1: 외부 테이블 행 삭제 실행 + // 🆕 v3.1: 외부 테이블 행 삭제 실행 (소프트 삭제 - _isDeleted 플래그 설정) const handleDeleteExternalRow = (cardId: string, rowId: string, contentRowId: string) => { const key = `${cardId}-${contentRowId}`; setExternalTableData((prev) => { const newData = { ...prev, - [key]: (prev[key] || []).filter((row) => row._rowId !== rowId), + [key]: (prev[key] || []).map((row) => + row._rowId === rowId + ? { ...row, _isDeleted: true, _isDirty: true } + : row + ), }; - // 🆕 v3.5: 행 삭제 시 집계 재계산 + // 🆕 v3.5: 행 삭제 시 집계 재계산 (삭제된 행 제외) setTimeout(() => { recalculateAggregationsWithExternalData(newData); }, 0); @@ -794,6 +835,27 @@ export function RepeatScreenModalComponent({ setPendingDeleteInfo(null); }; + // 🆕 v3.7: 삭제 취소 (소프트 삭제 복원) + const handleRestoreExternalRow = (cardId: string, rowId: string, contentRowId: string) => { + const key = `${cardId}-${contentRowId}`; + setExternalTableData((prev) => { + const newData = { + ...prev, + [key]: (prev[key] || []).map((row) => + row._rowId === rowId + ? { ...row, _isDeleted: false, _isDirty: true } + : row + ), + }; + + setTimeout(() => { + recalculateAggregationsWithExternalData(newData); + }, 0); + + return newData; + }); + }; + // 🆕 v3.1: 외부 테이블 행 데이터 변경 const handleExternalRowDataChange = (cardId: string, contentRowId: string, rowId: string, field: string, value: any) => { const key = `${cardId}-${contentRowId}`; @@ -1700,12 +1762,20 @@ export function RepeatScreenModalComponent({ (externalTableData[`${card._cardId}-${contentRow.id}`] || []).map((row) => ( {(contentRow.tableColumns || []).map((col) => ( {renderTableCell(col, row, (value) => handleExternalRowDataChange(card._cardId, contentRow.id, row._rowId, col.field, value) @@ -1714,14 +1784,26 @@ export function RepeatScreenModalComponent({ ))} {contentRow.tableCrud?.allowDelete && ( - + {row._isDeleted ? ( + + ) : ( + + )} )}