"use client"; import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Checkbox } from "@/components/ui/checkbox"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Textarea } from "@/components/ui/textarea"; import { Label } from "@/components/ui/label"; import { MoreHorizontal, Edit, Trash2, Copy, Eye, Plus, Search, Palette, RotateCcw, Trash } from "lucide-react"; import { ScreenDefinition } from "@/types/screen"; import { screenApi } from "@/lib/api/screen"; import CreateScreenModal from "./CreateScreenModal"; import CopyScreenModal from "./CopyScreenModal"; interface ScreenListProps { onScreenSelect: (screen: ScreenDefinition) => void; selectedScreen: ScreenDefinition | null; onDesignScreen: (screen: ScreenDefinition) => void; } type DeletedScreenDefinition = ScreenDefinition & { deletedDate?: Date; deletedBy?: string; deleteReason?: string; }; export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScreen }: ScreenListProps) { const [activeTab, setActiveTab] = useState("active"); const [screens, setScreens] = useState([]); const [deletedScreens, setDeletedScreens] = useState([]); const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(""); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [isCreateOpen, setIsCreateOpen] = useState(false); const [isCopyOpen, setIsCopyOpen] = useState(false); const [screenToCopy, setScreenToCopy] = useState(null); // 삭제 관련 상태 const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [screenToDelete, setScreenToDelete] = useState(null); const [deleteReason, setDeleteReason] = useState(""); const [dependencies, setDependencies] = useState< Array<{ screenId: number; screenName: string; screenCode: string; componentId: string; componentType: string; referenceType: string; }> >([]); const [showDependencyWarning, setShowDependencyWarning] = useState(false); const [checkingDependencies, setCheckingDependencies] = useState(false); // 영구 삭제 관련 상태 const [permanentDeleteDialogOpen, setPermanentDeleteDialogOpen] = useState(false); const [screenToPermanentDelete, setScreenToPermanentDelete] = useState(null); // 일괄삭제 관련 상태 const [selectedScreenIds, setSelectedScreenIds] = useState([]); const [bulkDeleteDialogOpen, setBulkDeleteDialogOpen] = useState(false); const [bulkDeleting, setBulkDeleting] = useState(false); // 화면 목록 로드 (실제 API) useEffect(() => { let abort = false; const load = async () => { try { setLoading(true); if (activeTab === "active") { const resp = await screenApi.getScreens({ page: currentPage, size: 20, searchTerm }); if (abort) return; setScreens(resp.data || []); setTotalPages(Math.max(1, Math.ceil((resp.total || 0) / 20))); } else if (activeTab === "trash") { const resp = await screenApi.getDeletedScreens({ page: currentPage, size: 20 }); if (abort) return; setDeletedScreens(resp.data || []); setTotalPages(Math.max(1, Math.ceil((resp.total || 0) / 20))); } } catch (e) { // console.error("화면 목록 조회 실패", e); if (activeTab === "active") { setScreens([]); } else { setDeletedScreens([]); } setTotalPages(1); } finally { if (!abort) setLoading(false); } }; load(); return () => { abort = true; }; }, [currentPage, searchTerm, activeTab]); const filteredScreens = screens; // 서버 필터 기준 사용 // 화면 목록 다시 로드 const reloadScreens = async () => { try { setLoading(true); const resp = await screenApi.getScreens({ page: currentPage, size: 20, searchTerm }); setScreens(resp.data || []); setTotalPages(Math.max(1, Math.ceil((resp.total || 0) / 20))); } catch (e) { // console.error("화면 목록 조회 실패", e); } finally { setLoading(false); } }; const handleScreenSelect = (screen: ScreenDefinition) => { onScreenSelect(screen); }; const handleEdit = (screen: ScreenDefinition) => { // 편집 모달 열기 // console.log("편집:", screen); }; const handleDelete = async (screen: ScreenDefinition) => { setScreenToDelete(screen); setCheckingDependencies(true); try { // 의존성 체크 const dependencyResult = await screenApi.checkScreenDependencies(screen.screenId); if (dependencyResult.hasDependencies) { setDependencies(dependencyResult.dependencies); setShowDependencyWarning(true); } else { setDeleteDialogOpen(true); } } catch (error) { // console.error("의존성 체크 실패:", error); // 의존성 체크 실패 시에도 삭제 다이얼로그는 열어줌 setDeleteDialogOpen(true); } finally { setCheckingDependencies(false); } }; const confirmDelete = async (force: boolean = false) => { if (!screenToDelete) return; try { await screenApi.deleteScreen(screenToDelete.screenId, deleteReason, force); setScreens((prev) => prev.filter((s) => s.screenId !== screenToDelete.screenId)); setDeleteDialogOpen(false); setShowDependencyWarning(false); setScreenToDelete(null); setDeleteReason(""); setDependencies([]); } catch (error: any) { // console.error("화면 삭제 실패:", error); // 의존성 오류인 경우 경고창 표시 if (error.response?.status === 409 && error.response?.data?.code === "SCREEN_HAS_DEPENDENCIES") { setDependencies(error.response.data.dependencies || []); setShowDependencyWarning(true); setDeleteDialogOpen(false); } else { alert("화면 삭제에 실패했습니다."); } } }; const handleCancelDelete = () => { setDeleteDialogOpen(false); setShowDependencyWarning(false); setScreenToDelete(null); setDeleteReason(""); setDependencies([]); }; const handleRestore = async (screen: DeletedScreenDefinition) => { if (!confirm(`"${screen.screenName}" 화면을 복원하시겠습니까?`)) return; try { await screenApi.restoreScreen(screen.screenId); setDeletedScreens((prev) => prev.filter((s) => s.screenId !== screen.screenId)); // 활성 탭으로 이동하여 복원된 화면 확인 setActiveTab("active"); reloadScreens(); } catch (error) { // console.error("화면 복원 실패:", error); alert("화면 복원에 실패했습니다."); } }; const handlePermanentDelete = (screen: DeletedScreenDefinition) => { setScreenToPermanentDelete(screen); setPermanentDeleteDialogOpen(true); }; const confirmPermanentDelete = async () => { if (!screenToPermanentDelete) return; try { await screenApi.permanentDeleteScreen(screenToPermanentDelete.screenId); setDeletedScreens((prev) => prev.filter((s) => s.screenId !== screenToPermanentDelete.screenId)); setPermanentDeleteDialogOpen(false); setScreenToPermanentDelete(null); } catch (error) { // console.error("화면 영구 삭제 실패:", error); alert("화면 영구 삭제에 실패했습니다."); } }; // 체크박스 선택 처리 const handleScreenCheck = (screenId: number, checked: boolean) => { if (checked) { setSelectedScreenIds((prev) => [...prev, screenId]); } else { setSelectedScreenIds((prev) => prev.filter((id) => id !== screenId)); } }; // 전체 선택/해제 const handleSelectAll = (checked: boolean) => { if (checked) { setSelectedScreenIds(deletedScreens.map((screen) => screen.screenId)); } else { setSelectedScreenIds([]); } }; // 일괄삭제 실행 const handleBulkDelete = () => { if (selectedScreenIds.length === 0) { alert("삭제할 화면을 선택해주세요."); return; } setBulkDeleteDialogOpen(true); }; const confirmBulkDelete = async () => { if (selectedScreenIds.length === 0) return; try { setBulkDeleting(true); const result = await screenApi.bulkPermanentDeleteScreens(selectedScreenIds); // 삭제된 화면들을 목록에서 제거 setDeletedScreens((prev) => prev.filter((screen) => !selectedScreenIds.includes(screen.screenId))); setSelectedScreenIds([]); setBulkDeleteDialogOpen(false); // 결과 메시지 표시 let message = `${result.deletedCount}개 화면이 영구 삭제되었습니다.`; if (result.skippedCount > 0) { message += `\n${result.skippedCount}개 화면은 삭제되지 않았습니다.`; } if (result.errors.length > 0) { message += `\n오류 발생: ${result.errors.map((e) => `화면 ${e.screenId}: ${e.error}`).join(", ")}`; } alert(message); } catch (error) { // console.error("일괄 삭제 실패:", error); alert("일괄 삭제에 실패했습니다."); } finally { setBulkDeleting(false); } }; const handleCopy = (screen: ScreenDefinition) => { setScreenToCopy(screen); setIsCopyOpen(true); }; const handleView = (screen: ScreenDefinition) => { // 미리보기 모달 열기 // console.log("미리보기:", screen); }; const handleCopySuccess = () => { // 복사 성공 후 화면 목록 다시 로드 reloadScreens(); }; if (loading) { return (
로딩 중...
); } return (
{/* 검색 및 필터 */}
setSearchTerm(e.target.value)} className="w-80 pl-10" disabled={activeTab === "trash"} />
{/* 탭 구조 */} 활성 화면 휴지통 {/* 활성 화면 탭 */} 화면 목록 ({screens.length}) 화면명 화면 코드 테이블명 상태 생성일 작업 {screens.map((screen) => ( handleScreenSelect(screen)} >
{screen.screenName}
{screen.description &&
{screen.description}
}
{screen.screenCode} {screen.tableLabel || screen.tableName} {screen.isActive === "Y" ? "활성" : "비활성"}
{screen.createdDate.toLocaleDateString()}
{screen.createdBy}
onDesignScreen(screen)}> 화면 설계 handleView(screen)}> 미리보기 handleEdit(screen)}> 편집 handleCopy(screen)}> 복사 handleDelete(screen)} className="text-red-600" disabled={checkingDependencies && screenToDelete?.screenId === screen.screenId} > {checkingDependencies && screenToDelete?.screenId === screen.screenId ? "확인 중..." : "삭제"}
))}
{filteredScreens.length === 0 && (
검색 결과가 없습니다.
)}
{/* 휴지통 탭 */} 휴지통 ({deletedScreens.length}) {selectedScreenIds.length > 0 && ( )} 0 && selectedScreenIds.length === deletedScreens.length} onCheckedChange={handleSelectAll} /> 화면명 화면 코드 테이블명 삭제일 삭제자 삭제 사유 작업 {deletedScreens.map((screen) => ( handleScreenCheck(screen.screenId, checked as boolean)} />
{screen.screenName}
{screen.description &&
{screen.description}
}
{screen.screenCode} {screen.tableLabel || screen.tableName}
{screen.deletedDate?.toLocaleDateString()}
{screen.deletedBy}
{screen.deleteReason || "-"}
))}
{deletedScreens.length === 0 && (
휴지통이 비어있습니다.
)}
{/* 페이지네이션 */} {totalPages > 1 && (
{currentPage} / {totalPages}
)} {/* 새 화면 생성 모달 */} { // 목록에 즉시 반영 (첫 페이지 기준 상단 추가) setScreens((prev) => [created, ...prev]); }} /> {/* 화면 복사 모달 */} setIsCopyOpen(false)} sourceScreen={screenToCopy} onCopySuccess={handleCopySuccess} /> {/* 삭제 확인 다이얼로그 */} 화면 삭제 확인 "{screenToDelete?.screenName}" 화면을 휴지통으로 이동하시겠습니까?
휴지통에서 언제든지 복원할 수 있습니다.