"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 { 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 { ResponsiveDataView, RDVColumn, RDVCardField } from "@/components/common/ResponsiveDataView"; import { ScrollToTop } from "@/components/common/ScrollToTop"; import { Plus, Search, Edit, Trash2, Copy, MoreVertical, AlertCircle, RefreshCw } from "lucide-react"; /** * 대시보드 관리 페이지 * - CSR 방식으로 초기 데이터 로드 * - 대시보드 목록 조회 * - 대시보드 생성/수정/삭제/복사 */ export default function DashboardListPage() { const router = useRouter(); const { toast } = useToast(); // 상태 관리 const [dashboards, setDashboards] = useState([]); const [loading, setLoading] = useState(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) => new Date(dateString).toLocaleDateString("ko-KR", { year: "numeric", month: "2-digit", day: "2-digit", }); // ResponsiveDataView 컬럼 정의 const columns: RDVColumn[] = [ { key: "title", label: "제목", render: (_v, row) => ( ), }, { key: "description", label: "설명", render: (_v, row) => ( {row.description || "-"} ), }, { key: "createdByName", label: "생성자", width: "120px", render: (_v, row) => row.createdByName || row.createdBy || "-", }, { key: "createdAt", label: "생성일", width: "120px", render: (_v, row) => formatDate(row.createdAt), }, { key: "updatedAt", label: "수정일", width: "120px", render: (_v, row) => formatDate(row.updatedAt), }, ]; // 모바일 카드 필드 정의 const cardFields: RDVCardField[] = [ { label: "설명", render: (d) => ( {d.description || "-"} ), }, { label: "생성자", render: (d) => d.createdByName || d.createdBy || "-" }, { label: "생성일", render: (d) => formatDate(d.createdAt) }, { label: "수정일", render: (d) => formatDate(d.updatedAt) }, ]; return (
{/* 페이지 헤더 */}

대시보드 관리

대시보드를 생성하고 관리할 수 있습니다

{/* 검색 및 액션 (반응형) */}
setSearchTerm(e.target.value)} className="h-10 pl-10 text-sm" />
{totalCount.toLocaleString()}
{/* 대시보드 목록 */} {error ? (

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

{error}

) : ( data={dashboards} columns={columns} keyExtractor={(d) => d.id} isLoading={loading} emptyMessage="대시보드가 없습니다." skeletonCount={10} cardTitle={(d) => d.title} cardSubtitle={(d) => d.id} cardFields={cardFields} onRowClick={(d) => router.push(`/admin/screenMng/dashboardList/${d.id}`)} renderActions={(d) => ( router.push(`/admin/screenMng/dashboardList/${d.id}`)} className="gap-2 text-sm" > 편집 handleCopy(d)} className="gap-2 text-sm"> 복사 handleDeleteClick(d.id, d.title)} className="text-destructive focus:text-destructive gap-2 text-sm" > 삭제 )} actionsLabel="작업" actionsWidth="80px" /> )} {/* 페이지네이션 */} {!loading && dashboards.length > 0 && ( )} {/* 삭제 확인 모달 */} "{deleteTarget?.title}" 대시보드를 삭제하시겠습니까?
이 작업은 되돌릴 수 없습니다. } onConfirm={handleDeleteConfirm} />
{/* Scroll to Top 버튼 */}
); }