"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 { useToast } from "@/hooks/use-toast"; import { Pagination, PaginationInfo } from "@/components/common/Pagination"; import { DeleteConfirmModal } from "@/components/common/DeleteConfirmModal"; import { Plus, Search, Edit, Trash2, Copy, MoreVertical, AlertCircle, RefreshCw } from "lucide-react"; /** * 대시보드 목록 클라이언트 컴포넌트 * - CSR 방식으로 초기 데이터 로드 * - 대시보드 목록 조회 * - 대시보드 생성/수정/삭제/복사 */ export default function DashboardListClient() { const router = useRouter(); const { toast } = useToast(); // 상태 관리 const [dashboards, setDashboards] = useState([]); const [loading, setLoading] = useState(true); // CSR이므로 초기 로딩 true const [error, setError] = useState(null); const [searchTerm, setSearchTerm] = useState(""); // 페이지네이션 상태 const [currentPage, setCurrentPage] = useState(1); const [pageSize, setPageSize] = useState(10); const [totalCount, setTotalCount] = useState(0); // 모달 상태 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); } }; // 검색어/페이지 변경 시 fetch (초기 로딩 포함) useEffect(() => { loadDashboards(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchTerm, currentPage, pageSize]); // 페이지네이션 정보 계산 const paginationInfo: PaginationInfo = { currentPage, totalPages: Math.ceil(totalCount / pageSize) || 1, 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" />
{totalCount.toLocaleString()}
{/* 대시보드 목록 */} {loading ? ( <> {/* 데스크톱 테이블 스켈레톤 */}
제목 설명 생성자 생성일 수정일 작업 {Array.from({ length: 10 }).map((_, index) => (
))}
{/* 모바일/태블릿 카드 스켈레톤 */}
{Array.from({ length: 6 }).map((_, index) => (
{Array.from({ length: 3 }).map((_, i) => (
))}
))}
) : error ? (

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

{error}

) : dashboards.length === 0 ? (

대시보드가 없습니다

) : ( <> {/* 데스크톱 테이블 뷰 (lg 이상) */}
제목 설명 생성자 생성일 수정일 작업 {dashboards.map((dashboard) => ( {dashboard.description || "-"} {dashboard.createdByName || dashboard.createdBy || "-"} {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" > 삭제 ))}
{/* 모바일/태블릿 카드 뷰 (lg 미만) */}
{dashboards.map((dashboard) => (
{/* 헤더 */}

{dashboard.id}

{/* 정보 */}
설명 {dashboard.description || "-"}
생성자 {dashboard.createdByName || dashboard.createdBy || "-"}
생성일 {formatDate(dashboard.createdAt)}
수정일 {formatDate(dashboard.updatedAt)}
{/* 액션 */}
))}
)} {/* 페이지네이션 */} {!loading && dashboards.length > 0 && ( )} {/* 삭제 확인 모달 */} "{deleteTarget?.title}" 대시보드를 삭제하시겠습니까?
이 작업은 되돌릴 수 없습니다. } onConfirm={handleDeleteConfirm} /> ); }