"use client"; import { useState, useRef, useEffect, useCallback } from "react"; import { ReportMaster } from "@/types/report"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Copy, Trash2, Edit, Eye, FileText, Calendar, User, Loader2, Pencil, AlignLeft } from "lucide-react"; import { reportApi } from "@/lib/api/reportApi"; import { useToast } from "@/hooks/use-toast"; import { useRouter } from "next/navigation"; import { format } from "date-fns"; import { getTypeBgClass, getTypeLabel, getTypeIcon } from "@/lib/reportTypeColors"; interface ReportListTableProps { reports: ReportMaster[]; total: number; page: number; limit: number; isLoading: boolean; viewMode: "grid" | "list"; onPageChange: (page: number) => void; onRefresh: () => void; onViewClick: (report: ReportMaster) => void; onCopyClick: (report: ReportMaster) => void; } export function ReportListTable({ reports, total, page, limit, isLoading, viewMode, onPageChange, onRefresh, onViewClick, onCopyClick, }: ReportListTableProps) { const [deleteTarget, setDeleteTarget] = useState(null); const [isDeleting, setIsDeleting] = useState(false); const { toast } = useToast(); const router = useRouter(); const totalPages = Math.ceil(total / limit); const handleEdit = (reportId: string) => { router.push(`/admin/screenMng/reportList/designer/${reportId}`); }; const handleDeleteConfirm = async () => { if (!deleteTarget) return; setIsDeleting(true); try { const response = await reportApi.deleteReport(deleteTarget); if (response.success) { toast({ title: "성공", description: "리포트가 삭제되었습니다." }); setDeleteTarget(null); onRefresh(); } } catch (error: any) { toast({ title: "오류", description: error.message || "리포트 삭제에 실패했습니다.", variant: "destructive", }); } finally { setIsDeleting(false); } }; const formatDate = (dateString: string | null) => { if (!dateString) return "-"; try { return format(new Date(dateString), "yyyy-MM-dd"); } catch { return dateString; } }; const formatUpdatedDate = (updatedAt: string | null, createdAt: string | null) => { if (!updatedAt) return "-"; try { const updatedStr = format(new Date(updatedAt), "yyyy-MM-dd HH:mm:ss"); const createdStr = createdAt ? format(new Date(createdAt), "yyyy-MM-dd HH:mm:ss") : null; if (createdStr && updatedStr === createdStr) return "-"; return format(new Date(updatedAt), "yyyy-MM-dd"); } catch { return updatedAt || "-"; } }; if (isLoading) { return (
); } if (reports.length === 0) { return (

등록된 리포트가 없습니다.

); } const handleRename = async (reportId: string, newName: string) => { try { const response = await reportApi.updateReport(reportId, { reportNameKor: newName }); if (response.success) { toast({ title: "성공", description: "리포트명이 변경되었습니다." }); onRefresh(); } } catch (error: any) { toast({ title: "오류", description: error.message || "리포트명 변경에 실패했습니다.", variant: "destructive", }); } }; const handleDescriptionChange = async (reportId: string, newDesc: string) => { try { const response = await reportApi.updateReport(reportId, { description: newDesc }); if (response.success) { toast({ title: "성공", description: "설명이 변경되었습니다." }); onRefresh(); } } catch (error: any) { toast({ title: "오류", description: error.message || "설명 변경에 실패했습니다.", variant: "destructive", }); } }; return ( <> {viewMode === "grid" ? ( ) : ( )} {/* 페이지네이션 */}
{total}건의 리포트 {totalPages > 1 && (
{Array.from({ length: totalPages }, (_, i) => i + 1).map((p) => ( ))}
)}
{/* 삭제 확인 다이얼로그 */} !open && setDeleteTarget(null)}> 리포트 삭제 이 리포트를 삭제하시겠습니까?
삭제된 리포트는 복구할 수 없습니다.
취소 {isDeleting ? ( <> 삭제 중... ) : ( "삭제" )}
); } interface ViewProps { reports: ReportMaster[]; page: number; limit: number; onEdit: (id: string) => void; onView: (report: ReportMaster) => void; onCopyClick: (report: ReportMaster) => void; onDeleteClick: (id: string) => void; onRename: (reportId: string, newName: string) => Promise; onDescriptionChange: (reportId: string, newDesc: string) => Promise; formatDate: (d: string | null) => string; formatUpdatedDate: (updatedAt: string | null, createdAt: string | null) => string; } function InlineReportName({ reportId, name, onNavigate, onRename, }: { reportId: string; name: string; onNavigate: () => void; onRename: (reportId: string, newName: string) => Promise; }) { const [isEditing, setIsEditing] = useState(false); const [editValue, setEditValue] = useState(name); const [isSaving, setIsSaving] = useState(false); const inputRef = useRef(null); useEffect(() => { if (isEditing) { inputRef.current?.focus(); inputRef.current?.select(); } }, [isEditing]); const handleSave = useCallback(async () => { const trimmed = editValue.trim(); if (!trimmed || trimmed === name) { setIsEditing(false); setEditValue(name); return; } setIsSaving(true); try { await onRename(reportId, trimmed); } finally { setIsSaving(false); setIsEditing(false); } }, [editValue, name, reportId, onRename]); const handleCancel = useCallback(() => { setIsEditing(false); setEditValue(name); }, [name]); const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { if (e.key === "Enter") { e.preventDefault(); handleSave(); } else if (e.key === "Escape") { handleCancel(); } }, [handleSave, handleCancel], ); if (isEditing) { return (
setEditValue(e.target.value)} onKeyDown={handleKeyDown} onBlur={handleSave} disabled={isSaving} className="h-7 text-sm font-medium" /> {isSaving && }
); } return (
); } function InlineDescription({ reportId, description, onSave, }: { reportId: string; description: string | null; onSave: (reportId: string, newDesc: string) => Promise; }) { const [isEditing, setIsEditing] = useState(false); const [editValue, setEditValue] = useState(description || ""); const [isSaving, setIsSaving] = useState(false); const inputRef = useRef(null); useEffect(() => { if (isEditing) { inputRef.current?.focus(); } }, [isEditing]); const handleSave = useCallback(async () => { const trimmed = editValue.trim(); if (trimmed === (description || "")) { setIsEditing(false); setEditValue(description || ""); return; } setIsSaving(true); try { await onSave(reportId, trimmed); } finally { setIsSaving(false); setIsEditing(false); } }, [editValue, description, reportId, onSave]); const handleCancel = useCallback(() => { setIsEditing(false); setEditValue(description || ""); }, [description]); const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { if (e.key === "Enter") { e.preventDefault(); handleSave(); } else if (e.key === "Escape") { handleCancel(); } }, [handleSave, handleCancel], ); if (isEditing) { return (
setEditValue(e.target.value)} onKeyDown={handleKeyDown} onBlur={handleSave} disabled={isSaving} placeholder="설명 입력" className="h-6 text-xs" /> {isSaving && }
); } return (
setIsEditing(true)} title={description || "클릭하여 설명 입력"} > {description || Inline Description}
); } function ListView({ reports, page, limit, onEdit, onView, onCopyClick, onDeleteClick, onRename, onDescriptionChange, formatDate, formatUpdatedDate }: ViewProps) { return (
{reports.map((report, index) => { const rowNumber = (page - 1) * limit + index + 1; return ( ); })}
NO 리포트명 설명 카테고리 작성자 생성일 수정일 관리
{rowNumber}
onEdit(report.report_id)} onRename={onRename} />
{report.report_type && (() => { const TypeIcon = getTypeIcon(report.report_type); return ( {getTypeLabel(report.report_type)} ); })()} {report.created_by || "-"} {formatDate(report.created_at)} {formatUpdatedDate(report.updated_at, report.created_at)}
); } function GridView({ reports, page, limit, onEdit, onView, onCopyClick, onDeleteClick, onRename, onDescriptionChange, formatDate, formatUpdatedDate }: ViewProps) { return (
{reports.map((report, index) => { const rowNumber = (page - 1) * limit + index + 1; return (
onEdit(report.report_id)} onRename={onRename} /> {report.report_type && (() => { const TypeIcon = getTypeIcon(report.report_type); return ( {getTypeLabel(report.report_type)} ); })()}
#{rowNumber}
작성자 {report.created_by || "-"}
생성일 {formatDate(report.created_at)}
수정일 {formatUpdatedDate(report.updated_at, report.created_at)}
설명
); })}
); }