Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node into feature/screen-management

This commit is contained in:
kjs
2025-10-08 09:45:59 +09:00
239 changed files with 14630 additions and 586582 deletions

View File

@@ -48,6 +48,7 @@ import { downloadFile, getLinkedFiles, getFilePreviewUrl, getDirectFileUrl } fro
import { toast } from "sonner";
import { FileUpload } from "@/components/screen/widgets/FileUpload";
import { AdvancedSearchFilters } from "./filters/AdvancedSearchFilters";
import { SaveModal } from "./SaveModal";
// 파일 데이터 타입 정의 (AttachedFileInfo와 호환)
interface FileInfo {
@@ -87,12 +88,14 @@ interface InteractiveDataTableProps {
component: DataTableComponent;
className?: string;
style?: React.CSSProperties;
onRefresh?: () => void; // 테이블 새로고침 콜백
}
export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
component,
className = "",
style = {},
onRefresh,
}) => {
const [data, setData] = useState<Record<string, any>[]>([]);
const [loading, setLoading] = useState(false);
@@ -101,13 +104,11 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
const [totalPages, setTotalPages] = useState(1);
const [total, setTotal] = useState(0);
const [selectedRows, setSelectedRows] = useState<Set<number>>(new Set());
const [showAddModal, setShowAddModal] = useState(false);
const [addFormData, setAddFormData] = useState<Record<string, any>>({});
const [isAdding, setIsAdding] = useState(false);
const [showEditModal, setShowEditModal] = useState(false);
const [editFormData, setEditFormData] = useState<Record<string, any>>({});
const [editingRowData, setEditingRowData] = useState<Record<string, any> | null>(null);
const [isEditing, setIsEditing] = useState(false);
// SaveModal 상태 (등록/수정 통합)
const [showSaveModal, setShowSaveModal] = useState(false);
const [saveModalData, setSaveModalData] = useState<Record<string, any> | undefined>(undefined);
const [saveModalScreenId, setSaveModalScreenId] = useState<number | undefined>(undefined);
// 이미지 미리보기 상태
const [previewImage, setPreviewImage] = useState<FileInfo | null>(null);
@@ -150,7 +151,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
return options;
}
} catch (error) {
console.error(`공통코드 옵션 로드 실패: ${categoryCode}`, error);
// console.error(`공통코드 옵션 로드 실패: ${categoryCode}`, error);
}
return [];
@@ -158,6 +159,22 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
[codeOptions],
);
// 🆕 전역 테이블 새로고침 이벤트 리스너
useEffect(() => {
const handleRefreshTable = () => {
console.log("🔄 InteractiveDataTable: 전역 새로고침 이벤트 수신");
if (component.tableName) {
loadData(currentPage, searchValues);
}
};
window.addEventListener("refreshTable", handleRefreshTable);
return () => {
window.removeEventListener("refreshTable", handleRefreshTable);
};
}, [currentPage, searchValues, loadData, component.tableName]);
// 파일 상태 확인 함수
const checkFileStatus = useCallback(
async (rowData: Record<string, any>) => {
@@ -176,7 +193,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
return { hasFiles, fileCount, files: response.files || [] };
} catch (error) {
console.error("파일 상태 확인 오류:", error);
// console.error("파일 상태 확인 오류:", error);
return { hasFiles: false, fileCount: 0, files: [] };
}
},
@@ -235,7 +252,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
return { hasFiles, fileCount, files, targetObjid };
} catch (error) {
console.error("컬럼별 파일 상태 확인 오류:", error);
// console.error("컬럼별 파일 상태 확인 오류:", error);
return { hasFiles: false, fileCount: 0, files: [], targetObjid: null };
}
},
@@ -301,13 +318,13 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
// 이미지 로딩 실패 시 대체 URL 시도
const handleImageError = useCallback(() => {
if (!imageLoadError && previewImage) {
console.error("이미지 로딩 실패:", previewImage);
// console.error("이미지 로딩 실패:", previewImage);
setImageLoadError(true);
// 대체 URL 생성 (직접 파일 경로 사용)
if (previewImage.path) {
const altUrl = getDirectFileUrl(previewImage.path);
console.log("대체 URL 시도:", altUrl);
// console.log("대체 URL 시도:", altUrl);
setAlternativeImageUrl(altUrl);
} else {
toast.error("이미지를 불러올 수 없습니다.");
@@ -365,7 +382,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
try {
return tableColumn?.detailSettings ? JSON.parse(tableColumn.detailSettings) : {};
} catch {
console.warn("상세 설정 파싱 실패:", tableColumn?.detailSettings);
// console.warn("상세 설정 파싱 실패:", tableColumn?.detailSettings);
return {};
}
},
@@ -483,7 +500,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
setFileStatusMap(statusMap);
});
} catch (error) {
console.error("❌ 테이블 데이터 조회 실패:", error);
// console.error("❌ 테이블 데이터 조회 실패:", error);
setData([]);
setTotal(0);
setTotalPages(1);
@@ -503,7 +520,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
setCurrentUser(response.data);
}
} catch (error) {
console.error("현재 사용자 정보 로드 실패:", error);
// console.error("현재 사용자 정보 로드 실패:", error);
}
};
@@ -514,40 +531,40 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
useEffect(() => {
const handleRefreshFileStatus = async (event: CustomEvent) => {
const { tableName, recordId, columnName, targetObjid, fileCount } = event.detail;
console.log("🔄 InteractiveDataTable 파일 상태 새로고침 이벤트 수신:", {
tableName,
recordId,
columnName,
targetObjid,
fileCount,
currentTableName: component.tableName
});
// console.log("🔄 InteractiveDataTable 파일 상태 새로고침 이벤트 수신:", {
// tableName,
// recordId,
// columnName,
// targetObjid,
// fileCount,
// currentTableName: component.tableName
// });
// 현재 테이블과 일치하는지 확인
if (tableName === component.tableName) {
// 해당 행의 파일 상태 업데이트
const columnKey = `${recordId}_${columnName}`;
setFileStatusMap(prev => ({
setFileStatusMap((prev) => ({
...prev,
[recordId]: { hasFiles: fileCount > 0, fileCount },
[columnKey]: { hasFiles: fileCount > 0, fileCount }
[columnKey]: { hasFiles: fileCount > 0, fileCount },
}));
console.log("✅ 파일 상태 업데이트 완료:", {
recordId,
columnKey,
hasFiles: fileCount > 0,
fileCount
});
// console.log("✅ 파일 상태 업데이트 완료:", {
// recordId,
// columnKey,
// hasFiles: fileCount > 0,
// fileCount
// });
}
};
if (typeof window !== 'undefined') {
window.addEventListener('refreshFileStatus', handleRefreshFileStatus as EventListener);
if (typeof window !== "undefined") {
window.addEventListener("refreshFileStatus", handleRefreshFileStatus as EventListener);
return () => {
window.removeEventListener('refreshFileStatus', handleRefreshFileStatus as EventListener);
window.removeEventListener("refreshFileStatus", handleRefreshFileStatus as EventListener);
};
}
}, [component.tableName]);
@@ -559,7 +576,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
const columns = await tableTypeApi.getColumns(component.tableName);
setTableColumns(columns);
} catch (error) {
console.error("테이블 컬럼 정보 로드 실패:", error);
// console.error("테이블 컬럼 정보 로드 실패:", error);
}
};
@@ -713,19 +730,21 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
}
});
setAddFormData(initialData);
setShowAddModal(true);
// SaveModal 열기 (등록 모드)
const screenId = component.addModalConfig?.screenId;
if (!screenId) {
toast.error("화면 설정이 필요합니다. 테이블 설정에서 추가 모달 화면을 지정해주세요.");
return;
}
// SaveModal 사용
setSaveModalData(undefined); // undefined = 등록 모드
setSaveModalScreenId(screenId);
setShowSaveModal(true);
}, [getDisplayColumns, generateAutoValue, component.addModalConfig]);
// 추가 폼 데이터 변경 핸들러
const handleAddFormChange = useCallback((columnName: string, value: any) => {
setAddFormData((prev) => ({
...prev,
[columnName]: value,
}));
}, []);
// 데이터 수정 핸들러
// 데이터 수정 핸들러 (SaveModal 사용)
const handleEditData = useCallback(() => {
if (selectedRows.size !== 1) return;
@@ -734,6 +753,13 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
if (!selectedRowData) return;
const screenId = component.addModalConfig?.screenId;
if (!screenId) {
toast.error("화면 설정이 필요합니다. 테이블 설정에서 수정 모달 화면을 지정해주세요.");
return;
}
// 수정할 데이터로 폼 초기화
const initialData: Record<string, any> = {};
const displayColumns = getDisplayColumns();
@@ -744,13 +770,13 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
setEditFormData(initialData);
setEditingRowData(selectedRowData);
// 수정 모달 설정에서 제목과 설명 가져오기
const editModalTitle = component.editModalConfig?.title || "";
const editModalDescription = component.editModalConfig?.description || "";
console.log("📝 수정 모달 설정:", { editModalTitle, editModalDescription });
setShowEditModal(true);
}, [selectedRows, data, getDisplayColumns, component.editModalConfig]);
@@ -827,7 +853,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
handleAddFormChange(columnName, fileNames);
}
} catch (error) {
console.error("파일 업로드 실패:", error);
// console.error("파일 업로드 실패:", error);
alert("파일 업로드에 실패했습니다.");
} finally {
setUploadingFiles((prev) => ({ ...prev, [columnName]: false }));
@@ -870,7 +896,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
{currentFiles.map((file, index) => (
<div key={index} className="flex items-center justify-between rounded border bg-gray-50 p-2">
<div className="flex items-center space-x-2">
<div className="text-xs text-gray-600">📄</div>
<div className="text-muted-foreground text-xs">📄</div>
<div>
<p className="text-sm font-medium">{file.name}</p>
<p className="text-xs text-gray-500">{(file.size / 1024).toFixed(1)} KB</p>
@@ -888,9 +914,9 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
</div>
))}
{isUploading && (
<div className="flex items-center space-x-2 rounded border bg-blue-50 p-2">
<div className="bg-accent flex items-center space-x-2 rounded border p-2">
<Loader2 className="h-4 w-4 animate-spin" />
<span className="text-sm text-blue-600"> ...</span>
<span className="text-primary text-sm"> ...</span>
</div>
)}
</div>
@@ -905,7 +931,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
setIsAdding(true);
// 실제 API 호출로 데이터 추가
console.log("🔥 추가할 데이터:", addFormData);
// console.log("🔥 추가할 데이터:", addFormData);
await tableTypeApi.addTableData(component.tableName, addFormData);
// 모달 닫기 및 폼 초기화
@@ -915,7 +941,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
// 첫 페이지로 이동하여 새 데이터 확인
loadData(1, searchValues);
} catch (error) {
console.error("데이터 추가 실패:", error);
// console.error("데이터 추가 실패:", error);
alert("데이터 추가에 실패했습니다.");
} finally {
setIsAdding(false);
@@ -928,8 +954,8 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
setIsEditing(true);
// 실제 API 호출로 데이터 수정
console.log("🔥 수정할 데이터:", editFormData);
console.log("🔥 원본 데이터:", editingRowData);
// console.log("🔥 수정할 데이터:", editFormData);
// console.log("🔥 원본 데이터:", editingRowData);
if (editingRowData) {
await tableTypeApi.editTableData(component.tableName, editingRowData, editFormData);
@@ -944,7 +970,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
loadData(currentPage, searchValues);
}
} catch (error) {
console.error("데이터 수정 실패:", error);
// console.error("데이터 수정 실패:", error);
alert("데이터 수정에 실패했습니다.");
} finally {
setIsEditing(false);
@@ -978,7 +1004,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
const selectedData = Array.from(selectedRows).map((index) => data[index]);
// 실제 삭제 API 호출
console.log("🗑️ 삭제할 데이터:", selectedData);
// console.log("🗑️ 삭제할 데이터:", selectedData);
await tableTypeApi.deleteTableData(component.tableName, selectedData);
// 선택 해제 및 다이얼로그 닫기
@@ -988,7 +1014,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
// 데이터 새로고침
loadData(currentPage, searchValues);
} catch (error) {
console.error("데이터 삭제 실패:", error);
// console.error("데이터 삭제 실패:", error);
alert("데이터 삭제에 실패했습니다.");
} finally {
setIsDeleting(false);
@@ -1551,7 +1577,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
// File 객체 유효성 검사
if (!(file instanceof File) && !(file instanceof Blob)) {
console.error("❌ 잘못된 파일 객체:", file);
// console.error("❌ 잘못된 파일 객체:", file);
toast.error("파일 객체가 손상되었습니다. 파일을 다시 업로드해주세요.");
return;
}
@@ -1567,7 +1593,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
toast.success(`${fileInfo.name} 다운로드가 완료되었습니다.`);
return;
} catch (error) {
console.error("❌ 로컬 파일 다운로드 오류:", error);
// console.error("❌ 로컬 파일 다운로드 오류:", error);
toast.error("로컬 파일 다운로드에 실패했습니다. 파일을 다시 업로드해주세요.");
return;
}
@@ -1587,7 +1613,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
toast.success(`${fileInfo.name} 다운로드가 완료되었습니다.`);
} catch (error) {
console.error("파일 다운로드 오류:", error);
// console.error("파일 다운로드 오류:", error);
toast.error(`${fileInfo.name} 다운로드에 실패했습니다.`);
}
}, []);
@@ -1596,7 +1622,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
const handleDeleteLinkedFile = useCallback(
async (fileId: string, fileName: string) => {
try {
console.log("🗑️ 파일 삭제 시작:", { fileId, fileName });
// console.log("🗑️ 파일 삭제 시작:", { fileId, fileName });
// 삭제 확인 다이얼로그
if (!confirm(`"${fileName}" 파일을 삭제하시겠습니까?`)) {
@@ -1612,7 +1638,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
});
const result = response.data;
console.log("📡 파일 삭제 API 응답:", result);
// console.log("📡 파일 삭제 API 응답:", result);
if (!result.success) {
throw new Error(result.message || "파일 삭제 실패");
@@ -1629,15 +1655,15 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
try {
const response = await getLinkedFiles(component.tableName, recordId);
setLinkedFiles(response.files || []);
console.log("📁 파일 목록 새로고침 완료:", response.files?.length || 0);
// console.log("📁 파일 목록 새로고침 완료:", response.files?.length || 0);
} catch (error) {
console.error("파일 목록 새로고침 실패:", error);
// console.error("파일 목록 새로고침 실패:", error);
}
}
console.log("✅ 파일 삭제 완료:", fileName);
// console.log("✅ 파일 삭제 완료:", fileName);
} catch (error) {
console.error("❌ 파일 삭제 실패:", error);
// console.error("❌ 파일 삭제 실패:", error);
toast.error(`"${fileName}" 파일 삭제에 실패했습니다.`);
}
},
@@ -1670,13 +1696,13 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
<Button
variant="ghost"
size="sm"
className="h-8 w-8 p-0 hover:bg-blue-50"
className="hover:bg-accent h-8 w-8 p-0"
onClick={() => handleColumnFileClick(rowData, column)}
title={hasFiles ? `${fileCount}개 파일 보기` : "파일 업로드"}
>
{hasFiles ? (
<div className="relative">
<FolderOpen className="h-4 w-4 text-blue-600" />
<FolderOpen className="text-primary h-4 w-4" />
{fileCount > 0 && (
<div className="absolute -top-1 -right-1 flex h-3 w-3 items-center justify-center rounded-full bg-blue-600 text-[10px] text-white">
{fileCount > 9 ? "9+" : fileCount}
@@ -1729,7 +1755,13 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
};
return (
<div className={cn("flex h-full flex-col rounded-xl border border-gray-200/60 bg-gradient-to-br from-white to-gray-50/30 shadow-sm", className)} style={{ ...style, minHeight: "680px" }}>
<div
className={cn(
"flex h-full flex-col rounded-xl border border-gray-200/60 bg-gradient-to-br from-white to-gray-50/30 shadow-sm",
className,
)}
style={{ ...style, minHeight: "680px" }}
>
{/* 헤더 */}
<div className="p-6 pb-3">
<div className="flex items-center justify-between">
@@ -1819,85 +1851,85 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
<div className="flex h-full flex-col">
{visibleColumns.length > 0 ? (
<>
<div className="rounded-lg border border-gray-200/60 bg-white shadow-sm overflow-hidden">
<div className="overflow-hidden rounded-lg border border-gray-200/60 bg-white shadow-sm">
<Table>
<TableHeader>
<TableRow>
{/* 체크박스 컬럼 (삭제 기능이 활성화된 경우) */}
{component.enableDelete && (
<TableHead className="w-12 px-4">
<Checkbox
checked={selectedRows.size === data.length && data.length > 0}
onCheckedChange={handleSelectAll}
/>
</TableHead>
)}
{visibleColumns.map((column: DataTableColumn) => (
<TableHead
key={column.id}
className="px-4 font-semibold text-gray-700 bg-gradient-to-r from-gray-50 to-slate-50"
style={{ width: `${((column.gridColumns || 2) / totalGridColumns) * 100}%` }}
>
{column.label}
</TableHead>
))}
{/* 자동 파일 컬럼 표시 제거됨 - 명시적으로 추가된 파일 컬럼만 표시 */}
</TableRow>
</TableHeader>
<TableBody>
{loading ? (
<TableHeader>
<TableRow>
<TableCell
colSpan={visibleColumns.length + (component.enableDelete ? 1 : 0)}
className="h-32 text-center"
>
<div className="text-muted-foreground flex items-center justify-center gap-2">
<Loader2 className="h-4 w-4 animate-spin" />
...
</div>
</TableCell>
{/* 체크박스 컬럼 (삭제 기능이 활성화된 경우) */}
{component.enableDelete && (
<TableHead className="w-12 px-4">
<Checkbox
checked={selectedRows.size === data.length && data.length > 0}
onCheckedChange={handleSelectAll}
/>
</TableHead>
)}
{visibleColumns.map((column: DataTableColumn) => (
<TableHead
key={column.id}
className="bg-gradient-to-r from-gray-50 to-slate-50 px-4 font-semibold text-gray-700"
style={{ width: `${((column.gridColumns || 2) / totalGridColumns) * 100}%` }}
>
{column.label}
</TableHead>
))}
{/* 자동 파일 컬럼 표시 제거됨 - 명시적으로 추가된 파일 컬럼만 표시 */}
</TableRow>
) : data.length > 0 ? (
data.map((row, rowIndex) => (
<TableRow key={rowIndex} className="hover:bg-gradient-to-r hover:from-blue-50/50 hover:to-indigo-50/30 transition-all duration-200">
{/* 체크박스 셀 (삭제 기능이 활성화된 경우) */}
{component.enableDelete && (
<TableCell className="w-12 px-4">
<Checkbox
checked={selectedRows.has(rowIndex)}
onCheckedChange={(checked) => handleRowSelect(rowIndex, checked as boolean)}
/>
</TableCell>
)}
{visibleColumns.map((column: DataTableColumn) => (
<TableCell key={column.id} className="px-4 text-sm font-medium text-gray-900">
{formatCellValue(row[column.columnName], column, row)}
</TableCell>
))}
{/* 자동 파일 셀 표시 제거됨 - 명시적으로 추가된 파일 컬럼만 표시 */}
</TableHeader>
<TableBody>
{loading ? (
<TableRow>
<TableCell
colSpan={visibleColumns.length + (component.enableDelete ? 1 : 0)}
className="h-32 text-center"
>
<div className="text-muted-foreground flex items-center justify-center gap-2">
<Loader2 className="h-4 w-4 animate-spin" />
...
</div>
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={visibleColumns.length + (component.enableDelete ? 1 : 0)}
className="h-32 text-center"
>
<div className="text-muted-foreground flex flex-col items-center gap-2">
<Database className="h-8 w-8" />
<p> </p>
<p className="text-xs"> </p>
</div>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
) : data.length > 0 ? (
data.map((row, rowIndex) => (
<TableRow key={rowIndex} className="transition-all duration-200 hover:bg-orange-100">
{/* 체크박스 셀 (삭제 기능이 활성화된 경우) */}
{component.enableDelete && (
<TableCell className="w-12 px-4">
<Checkbox
checked={selectedRows.has(rowIndex)}
onCheckedChange={(checked) => handleRowSelect(rowIndex, checked as boolean)}
/>
</TableCell>
)}
{visibleColumns.map((column: DataTableColumn) => (
<TableCell key={column.id} className="px-4 text-sm font-medium text-gray-900">
{formatCellValue(row[column.columnName], column, row)}
</TableCell>
))}
{/* 자동 파일 셀 표시 제거됨 - 명시적으로 추가된 파일 컬럼만 표시 */}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={visibleColumns.length + (component.enableDelete ? 1 : 0)}
className="h-32 text-center"
>
<div className="text-muted-foreground flex flex-col items-center gap-2">
<Database className="h-8 w-8" />
<p> </p>
<p className="text-xs"> </p>
</div>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
{/* 페이지네이션 */}
{component.pagination?.enabled && totalPages > 1 && (
<div className="bg-gradient-to-r from-gray-50 to-slate-50 mt-auto border-t border-gray-200/60">
<div className="mt-auto border-t border-gray-200/60 bg-gradient-to-r from-gray-50 to-slate-50">
<div className="flex items-center justify-between px-6 py-3">
{component.pagination.showPageInfo && (
<div className="text-muted-foreground text-sm">
@@ -1971,8 +2003,26 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
</div>
</div>
{/* 데이터 추가 모달 */}
<Dialog open={showAddModal} onOpenChange={handleAddModalClose}>
{/* SaveModal (등록/수정 통합) */}
<SaveModal
isOpen={showSaveModal}
onClose={() => {
setShowSaveModal(false);
setSaveModalData(undefined);
setSaveModalScreenId(undefined);
}}
screenId={saveModalScreenId}
modalSize={component.addModalConfig?.modalSize || "lg"}
initialData={saveModalData}
onSaveSuccess={() => {
// 저장 성공 시 테이블 새로고침
loadData(currentPage, searchValues); // 현재 페이지로 다시 로드
setSelectedRows(new Set()); // 선택 해제
}}
/>
{/* 기존 데이터 추가 모달 (제거 예정 - SaveModal로 대체됨) */}
<Dialog open={false} onOpenChange={() => {}}>
<DialogContent className={`max-h-[80vh] overflow-y-auto ${getModalSizeClass()}`}>
<DialogHeader>
<DialogTitle>{component.addModalConfig?.title || "새 데이터 추가"}</DialogTitle>
@@ -2017,18 +2067,8 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
</DialogContent>
</Dialog>
{/* 데이터 수정 모달 */}
<Dialog
open={showEditModal}
onOpenChange={(open) => {
if (!isEditing && !open) {
setShowEditModal(false);
setEditFormData({});
setEditingRowData(null);
setUploadedFiles({}); // 파일 상태 초기화
}
}}
>
{/* 기존 데이터 수정 모달 (제거 예정 - SaveModal로 대체됨) */}
<Dialog open={false} onOpenChange={() => {}}>
<DialogContent className={`max-h-[80vh] overflow-y-auto ${getModalSizeClass()}`}>
<DialogHeader>
<DialogTitle> </DialogTitle>
@@ -2121,7 +2161,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
<h4 className="truncate font-medium text-gray-900" title={fileInfo.name}>
{fileInfo.name}
</h4>
<div className="mt-1 space-y-1 text-sm text-gray-600">
<div className="text-muted-foreground mt-1 space-y-1 text-sm">
<div className="flex items-center gap-4">
<span>: {(fileInfo.size / 1024 / 1024).toFixed(2)} MB</span>
<span>: {fileInfo.type || "알 수 없음"}</span>
@@ -2167,7 +2207,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
{/* 요약 정보 */}
{currentFileData && (
<div className="mt-4 rounded-lg border border-blue-200 bg-blue-50 p-3">
<div className="border-primary/20 bg-accent mt-4 rounded-lg border p-3">
<h5 className="mb-2 font-medium text-blue-900"> </h5>
<div className="grid grid-cols-2 gap-4 text-sm text-blue-800">
<div>
@@ -2202,7 +2242,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
<DialogDescription>
<strong>{selectedRows.size}</strong> ?
<br />
<span className="text-red-600"> .</span>
<span className="text-destructive"> .</span>
</DialogDescription>
</DialogHeader>
@@ -2311,7 +2351,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
{linkedFiles.map((file: any, index: number) => (
<div key={index} className="flex items-center justify-between rounded-lg border p-3">
<div className="flex items-center space-x-3">
<File className="h-5 w-5 text-blue-600" />
<File className="text-primary h-5 w-5" />
<div>
<div className="font-medium">{file.realFileName}</div>
<div className="text-sm text-gray-500">
@@ -2370,7 +2410,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
size="sm"
variant="outline"
onClick={() => handleDeleteLinkedFile(file.objid, file.realFileName)}
className="text-red-500 hover:bg-red-50 hover:text-red-700"
className="hover:bg-destructive/10 text-red-500 hover:text-red-700"
>
<Trash2 className="h-4 w-4" />
</Button>