"use client"; import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import { dashboardApi } from "@/lib/api/dashboard"; import { Dashboard } from "@/lib/api/dashboard"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; 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 { useToast } from "@/hooks/use-toast"; import { Pagination, PaginationInfo } from "@/components/common/Pagination"; import { Plus, Search, Edit, Trash2, Copy, MoreVertical, AlertCircle, RefreshCw } from "lucide-react"; interface DashboardListClientProps { initialDashboards: Dashboard[]; initialPagination: { total: number; page: number; limit: number; }; } /** * 대시보드 목록 클라이언트 컴포넌트 * - 대시보드 목록 조회 * - 대시보드 생성/수정/삭제/복사 */ export default function DashboardListClient({ initialDashboards, initialPagination }: DashboardListClientProps) { const router = useRouter(); const { toast } = useToast(); const [dashboards, setDashboards] = useState(initialDashboards); const [loading, setLoading] = useState(false); // 초기 로딩은 서버에서 완료 const [error, setError] = useState(null); const [searchTerm, setSearchTerm] = useState(""); // 페이지네이션 상태 const [currentPage, setCurrentPage] = useState(initialPagination.page); const [pageSize, setPageSize] = useState(initialPagination.limit); const [totalCount, setTotalCount] = useState(initialPagination.total); // 모달 상태 const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [deleteTarget, setDeleteTarget] = useState<{ id: string; title: string } | null>(null); // 대시보드 목록 로드 const loadDashboards = async () => { try { setLoading(true); setError(null); const result = await dashboardApi.getMyDashboards({ search: searchTerm, page: currentPage, limit: pageSize, }); setDashboards(result.dashboards); setTotalCount(result.pagination.total); } catch (err) { console.error("Failed to load dashboards:", err); setError( err instanceof Error ? err.message : "대시보드 목록을 불러오는데 실패했습니다. 네트워크 연결을 확인하거나 잠시 후 다시 시도해주세요.", ); } finally { setLoading(false); } }; // 초기 로드 여부 추적 const [isInitialLoad, setIsInitialLoad] = useState(true); useEffect(() => { // 초기 로드는 건너뛰기 (서버에서 이미 데이터를 가져왔음) if (isInitialLoad) { setIsInitialLoad(false); return; } // 이후 검색어/페이지 변경 시에만 fetch loadDashboards(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchTerm, currentPage, pageSize]); // 페이지네이션 정보 계산 const paginationInfo: PaginationInfo = { currentPage, totalPages: Math.ceil(totalCount / pageSize), totalItems: totalCount, itemsPerPage: pageSize, startItem: totalCount === 0 ? 0 : (currentPage - 1) * pageSize + 1, endItem: Math.min(currentPage * pageSize, totalCount), }; // 페이지 변경 핸들러 const handlePageChange = (page: number) => { setCurrentPage(page); }; // 페이지 크기 변경 핸들러 const handlePageSizeChange = (size: number) => { setPageSize(size); setCurrentPage(1); // 페이지 크기 변경 시 첫 페이지로 }; // 대시보드 삭제 확인 모달 열기 const handleDeleteClick = (id: string, title: string) => { setDeleteTarget({ id, title }); setDeleteDialogOpen(true); }; // 대시보드 삭제 실행 const handleDeleteConfirm = async () => { if (!deleteTarget) return; try { await dashboardApi.deleteDashboard(deleteTarget.id); setDeleteDialogOpen(false); setDeleteTarget(null); toast({ title: "성공", description: "대시보드가 삭제되었습니다.", }); loadDashboards(); } catch (err) { console.error("Failed to delete dashboard:", err); setDeleteDialogOpen(false); toast({ title: "오류", description: "대시보드 삭제에 실패했습니다.", variant: "destructive", }); } }; // 대시보드 복사 const handleCopy = async (dashboard: Dashboard) => { try { const fullDashboard = await dashboardApi.getDashboard(dashboard.id); await dashboardApi.createDashboard({ title: `${fullDashboard.title} (복사본)`, description: fullDashboard.description, elements: fullDashboard.elements || [], isPublic: false, tags: fullDashboard.tags, category: fullDashboard.category, settings: fullDashboard.settings as { resolution?: string; backgroundColor?: string }, }); toast({ title: "성공", description: "대시보드가 복사되었습니다.", }); loadDashboards(); } catch (err) { console.error("Failed to copy dashboard:", err); toast({ title: "오류", description: "대시보드 복사에 실패했습니다.", variant: "destructive", }); } }; // 포맷팅 헬퍼 const formatDate = (dateString: string) => { return new Date(dateString).toLocaleDateString("ko-KR", { year: "numeric", month: "2-digit", day: "2-digit", }); }; return ( <> {/* 검색 및 액션 */}
setSearchTerm(e.target.value)} className="h-10 pl-10 text-sm" />
{/* 대시보드 목록 */} {loading ? (
로딩 중...
대시보드 목록을 불러오고 있습니다
) : error ? (

데이터를 불러올 수 없습니다

{error}

) : dashboards.length === 0 ? (

대시보드가 없습니다

) : (
제목 설명 생성일 수정일 작업 {dashboards.map((dashboard) => ( {dashboard.title} {dashboard.description || "-"} {formatDate(dashboard.createdAt)} {formatDate(dashboard.updatedAt)} router.push(`/admin/dashboard/edit/${dashboard.id}`)} className="gap-2 text-sm" > 편집 handleCopy(dashboard)} className="gap-2 text-sm"> 복사 handleDeleteClick(dashboard.id, dashboard.title)} className="text-destructive focus:text-destructive gap-2 text-sm" > 삭제 ))}
)} {/* 페이지네이션 */} {!loading && dashboards.length > 0 && ( )} {/* 삭제 확인 모달 */} 대시보드 삭제 "{deleteTarget?.title}" 대시보드를 삭제하시겠습니까?
이 작업은 되돌릴 수 없습니다.
취소 삭제
); }