카드 디스플레이 삭제기능 구현
This commit is contained in:
parent
6449eb5ac3
commit
cb38864ad8
|
|
@ -55,6 +55,7 @@ import { useScreenPreview } from "@/contexts/ScreenPreviewContext";
|
|||
import { useTableOptions } from "@/contexts/TableOptionsContext";
|
||||
import { TableFilter, ColumnVisibility } from "@/types/table-options";
|
||||
import { useSplitPanelContext } from "@/contexts/SplitPanelContext";
|
||||
import { useScreenContextOptional } from "@/contexts/ScreenContext";
|
||||
import { useCascadingDropdown } from "@/hooks/useCascadingDropdown";
|
||||
import { CascadingDropdownConfig } from "@/types/screen-management";
|
||||
|
||||
|
|
@ -184,6 +185,8 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
|
|||
const { user } = useAuth(); // 사용자 정보 가져오기
|
||||
const { registerTable, unregisterTable } = useTableOptions(); // Context 훅
|
||||
const splitPanelContext = useSplitPanelContext(); // 분할 패널 컨텍스트
|
||||
const screenContext = useScreenContextOptional(); // 화면 컨텍스트 (좌측/우측 위치 확인용)
|
||||
const splitPanelPosition = screenContext?.splitPanelPosition; // 분할 패널 내 위치
|
||||
|
||||
const [data, setData] = useState<Record<string, any>[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
|
@ -947,7 +950,18 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
|
|||
}
|
||||
return newSet;
|
||||
});
|
||||
}, []);
|
||||
|
||||
// 분할 패널 좌측에서 선택 시 컨텍스트에 데이터 저장 (연결 필터용)
|
||||
if (splitPanelContext && splitPanelPosition === "left" && !splitPanelContext.disableAutoDataTransfer) {
|
||||
if (isSelected && data[rowIndex]) {
|
||||
splitPanelContext.setSelectedLeftData(data[rowIndex]);
|
||||
console.log("🔗 [InteractiveDataTable] 좌측 선택 데이터 저장:", data[rowIndex]);
|
||||
} else if (!isSelected) {
|
||||
splitPanelContext.setSelectedLeftData(null);
|
||||
console.log("🔗 [InteractiveDataTable] 좌측 선택 데이터 초기화");
|
||||
}
|
||||
}
|
||||
}, [data, splitPanelContext, splitPanelPosition]);
|
||||
|
||||
// 전체 선택/해제 핸들러
|
||||
const handleSelectAll = useCallback(
|
||||
|
|
|
|||
|
|
@ -108,6 +108,65 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
|||
setEditModalOpen(true);
|
||||
};
|
||||
|
||||
// 삭제 핸들러
|
||||
const handleCardDelete = async (data: any, index: number) => {
|
||||
// 사용자 확인
|
||||
if (!confirm("정말로 이 항목을 삭제하시겠습니까?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const tableNameToUse = tableName || component.componentConfig?.tableName;
|
||||
if (!tableNameToUse) {
|
||||
alert("테이블 정보가 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 삭제할 데이터를 배열로 감싸기 (API가 배열을 기대함)
|
||||
const deleteData = [data];
|
||||
|
||||
console.log("🗑️ [CardDisplay] 삭제 요청:", {
|
||||
tableName: tableNameToUse,
|
||||
data: deleteData,
|
||||
});
|
||||
|
||||
// API 호출로 데이터 삭제 (POST 방식으로 변경 - DELETE는 body 전달이 불안정)
|
||||
// 백엔드 API는 DELETE /api/table-management/tables/:tableName/delete 이지만
|
||||
// axios에서 DELETE body 전달 문제가 있어 직접 request 설정 사용
|
||||
const response = await apiClient.request({
|
||||
method: 'DELETE',
|
||||
url: `/table-management/tables/${tableNameToUse}/delete`,
|
||||
data: deleteData,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
console.log("삭제 완료:", response.data.data?.deletedCount || 1, "건");
|
||||
alert("삭제되었습니다.");
|
||||
|
||||
// 로컬 상태에서 삭제된 항목 제거
|
||||
setLoadedTableData(prev => prev.filter((item, idx) => idx !== index));
|
||||
|
||||
// 선택된 항목이면 선택 해제
|
||||
const cardKey = getCardKey(data, index);
|
||||
if (selectedRows.has(cardKey)) {
|
||||
const newSelectedRows = new Set(selectedRows);
|
||||
newSelectedRows.delete(cardKey);
|
||||
setSelectedRows(newSelectedRows);
|
||||
}
|
||||
} else {
|
||||
console.error("삭제 실패:", response.data.error);
|
||||
alert(`삭제 실패: ${response.data.message || response.data.error || "알 수 없는 오류"}`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("삭제 중 오류 발생:", error);
|
||||
const errorMessage = error.response?.data?.message || error.message || "알 수 없는 오류";
|
||||
alert(`삭제 중 오류가 발생했습니다: ${errorMessage}`);
|
||||
}
|
||||
};
|
||||
|
||||
// 편집 폼 데이터 변경 핸들러
|
||||
const handleEditFormChange = (key: string, value: string) => {
|
||||
setEditData((prev: any) => ({
|
||||
|
|
@ -155,15 +214,72 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
|||
return;
|
||||
}
|
||||
|
||||
// 연결 필터 확인 (분할 패널 내부일 때)
|
||||
let linkedFilterValues: Record<string, any> = {};
|
||||
let hasLinkedFiltersConfigured = false;
|
||||
let hasSelectedLeftData = false;
|
||||
|
||||
if (splitPanelContext) {
|
||||
// 연결 필터 설정 여부 확인 (현재 테이블에 해당하는 필터가 있는지)
|
||||
const linkedFiltersConfig = splitPanelContext.linkedFilters || [];
|
||||
hasLinkedFiltersConfigured = linkedFiltersConfig.some(
|
||||
(filter) => filter.targetColumn?.startsWith(tableNameToUse + ".") ||
|
||||
filter.targetColumn === tableNameToUse
|
||||
);
|
||||
|
||||
// 좌측 데이터 선택 여부 확인
|
||||
hasSelectedLeftData = splitPanelContext.selectedLeftData &&
|
||||
Object.keys(splitPanelContext.selectedLeftData).length > 0;
|
||||
|
||||
linkedFilterValues = splitPanelContext.getLinkedFilterValues();
|
||||
// 현재 테이블에 해당하는 필터만 추출 (테이블명.컬럼명 형식에서)
|
||||
const tableSpecificFilters: Record<string, any> = {};
|
||||
for (const [key, value] of Object.entries(linkedFilterValues)) {
|
||||
// key가 "테이블명.컬럼명" 형식인 경우
|
||||
if (key.includes(".")) {
|
||||
const [tblName, columnName] = key.split(".");
|
||||
if (tblName === tableNameToUse) {
|
||||
tableSpecificFilters[columnName] = value;
|
||||
hasLinkedFiltersConfigured = true;
|
||||
}
|
||||
} else {
|
||||
// 테이블명 없이 컬럼명만 있는 경우 그대로 사용
|
||||
tableSpecificFilters[key] = value;
|
||||
}
|
||||
}
|
||||
linkedFilterValues = tableSpecificFilters;
|
||||
|
||||
console.log("🎴 [CardDisplay] 연결 필터 확인:", {
|
||||
tableNameToUse,
|
||||
hasLinkedFiltersConfigured,
|
||||
hasSelectedLeftData,
|
||||
linkedFilterValues,
|
||||
});
|
||||
}
|
||||
|
||||
// 연결 필터가 설정되어 있지만 좌측에서 데이터가 선택되지 않은 경우 빈 데이터 표시
|
||||
if (splitPanelContext && hasLinkedFiltersConfigured && !hasSelectedLeftData) {
|
||||
console.log("🎴 [CardDisplay] 연결 필터 활성화됨 - 좌측 선택 대기");
|
||||
setLoadedTableData([]);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// API 호출 파라미터에 연결 필터 추가 (search 객체 안에 넣어야 함)
|
||||
const apiParams: Record<string, any> = {
|
||||
page: 1,
|
||||
size: 50, // 카드 표시용으로 적당한 개수
|
||||
search: Object.keys(linkedFilterValues).length > 0 ? linkedFilterValues : undefined,
|
||||
};
|
||||
|
||||
console.log("🎴 [CardDisplay] API 호출 파라미터:", apiParams);
|
||||
|
||||
// 테이블 데이터, 컬럼 정보, 입력 타입 정보를 병렬로 로드
|
||||
const [dataResponse, columnsResponse, inputTypesResponse] = await Promise.all([
|
||||
tableTypeApi.getTableData(tableNameToUse, {
|
||||
page: 1,
|
||||
size: 50, // 카드 표시용으로 적당한 개수
|
||||
}),
|
||||
tableTypeApi.getTableData(tableNameToUse, apiParams),
|
||||
tableTypeApi.getColumns(tableNameToUse),
|
||||
tableTypeApi.getColumnInputTypes(tableNameToUse),
|
||||
]);
|
||||
|
|
@ -232,7 +348,7 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
|||
};
|
||||
|
||||
loadTableData();
|
||||
}, [isDesignMode, tableName, component.componentConfig?.tableName]);
|
||||
}, [isDesignMode, tableName, component.componentConfig?.tableName, splitPanelContext?.selectedLeftData]);
|
||||
|
||||
// 컴포넌트 설정 (기본값 보장)
|
||||
const componentConfig = {
|
||||
|
|
@ -957,6 +1073,17 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
|
|||
편집
|
||||
</button>
|
||||
)}
|
||||
{(componentConfig.cardStyle?.showDeleteButton ?? false) && (
|
||||
<button
|
||||
className="text-xs text-red-500 hover:text-red-700 transition-colors"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleCardDelete(data, index);
|
||||
}}
|
||||
>
|
||||
삭제
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -306,6 +306,19 @@ export const CardDisplayConfigPanel: React.FC<CardDisplayConfigPanelProps> = ({
|
|||
편집 버튼
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="showDeleteButton"
|
||||
checked={config.cardStyle?.showDeleteButton ?? false}
|
||||
onChange={(e) => handleNestedChange("cardStyle.showDeleteButton", e.target.checked)}
|
||||
className="rounded border-gray-300"
|
||||
/>
|
||||
<label htmlFor="showDeleteButton" className="text-xs text-gray-600">
|
||||
삭제 버튼
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export interface CardStyleConfig {
|
|||
showActions?: boolean; // 액션 버튼 표시 여부 (전체)
|
||||
showViewButton?: boolean; // 상세보기 버튼 표시 여부
|
||||
showEditButton?: boolean; // 편집 버튼 표시 여부
|
||||
showDeleteButton?: boolean; // 삭제 버튼 표시 여부
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue