diff --git a/frontend/app/(main)/admin/report/page.tsx b/frontend/app/(main)/admin/report/page.tsx new file mode 100644 index 00000000..11c3e89d --- /dev/null +++ b/frontend/app/(main)/admin/report/page.tsx @@ -0,0 +1,111 @@ +"use client"; + +import { useState } from "react"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { ReportListTable } from "@/components/report/ReportListTable"; +import { ReportCreateModal } from "@/components/report/ReportCreateModal"; +import { Plus, Search, RotateCcw } from "lucide-react"; +import { useReportList } from "@/hooks/useReportList"; + +export default function ReportManagementPage() { + const [searchText, setSearchText] = useState(""); + const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); + + const { reports, total, page, limit, isLoading, refetch, setPage, handleSearch } = useReportList(); + + const handleSearchClick = () => { + handleSearch(searchText); + }; + + const handleReset = () => { + setSearchText(""); + handleSearch(""); + }; + + const handleCreateSuccess = () => { + setIsCreateModalOpen(false); + refetch(); + }; + + return ( +
+
+ {/* 페이지 제목 */} +
+
+

리포트 관리

+

리포트를 생성하고 관리합니다

+
+ +
+ + {/* 검색 영역 */} + + + + + 검색 + + + +
+ setSearchText(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") { + handleSearchClick(); + } + }} + className="flex-1" + /> + + +
+
+
+ + {/* 리포트 목록 */} + + + + + 📋 리포트 목록 + (총 {total}건) + + + + + + + +
+ + {/* 리포트 생성 모달 */} + setIsCreateModalOpen(false)} + onSuccess={handleCreateSuccess} + /> +
+ ); +} diff --git a/frontend/components/report/ReportCreateModal.tsx b/frontend/components/report/ReportCreateModal.tsx new file mode 100644 index 00000000..56644632 --- /dev/null +++ b/frontend/components/report/ReportCreateModal.tsx @@ -0,0 +1,228 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Loader2 } from "lucide-react"; +import { reportApi } from "@/lib/api/reportApi"; +import { useToast } from "@/hooks/use-toast"; +import { CreateReportRequest, ReportTemplate } from "@/types/report"; + +interface ReportCreateModalProps { + isOpen: boolean; + onClose: () => void; + onSuccess: () => void; +} + +export function ReportCreateModal({ isOpen, onClose, onSuccess }: ReportCreateModalProps) { + const [formData, setFormData] = useState({ + reportNameKor: "", + reportNameEng: "", + templateId: "", + reportType: "BASIC", + description: "", + }); + const [templates, setTemplates] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [isLoadingTemplates, setIsLoadingTemplates] = useState(false); + const { toast } = useToast(); + + // 템플릿 목록 불러오기 + useEffect(() => { + if (isOpen) { + fetchTemplates(); + } + }, [isOpen]); + + const fetchTemplates = async () => { + setIsLoadingTemplates(true); + try { + const response = await reportApi.getTemplates(); + if (response.success && response.data) { + setTemplates([...response.data.system, ...response.data.custom]); + } + } catch (error: any) { + toast({ + title: "오류", + description: "템플릿 목록을 불러오는데 실패했습니다.", + variant: "destructive", + }); + } finally { + setIsLoadingTemplates(false); + } + }; + + const handleSubmit = async () => { + // 유효성 검증 + if (!formData.reportNameKor.trim()) { + toast({ + title: "입력 오류", + description: "리포트명(한글)을 입력해주세요.", + variant: "destructive", + }); + return; + } + + if (!formData.reportType) { + toast({ + title: "입력 오류", + description: "리포트 타입을 선택해주세요.", + variant: "destructive", + }); + return; + } + + setIsLoading(true); + try { + const response = await reportApi.createReport(formData); + if (response.success) { + toast({ + title: "성공", + description: "리포트가 생성되었습니다.", + }); + handleClose(); + onSuccess(); + } + } catch (error: any) { + toast({ + title: "오류", + description: error.message || "리포트 생성에 실패했습니다.", + variant: "destructive", + }); + } finally { + setIsLoading(false); + } + }; + + const handleClose = () => { + setFormData({ + reportNameKor: "", + reportNameEng: "", + templateId: "", + reportType: "BASIC", + description: "", + }); + onClose(); + }; + + return ( + + + + 새 리포트 생성 + 새로운 리포트를 생성합니다. 필수 항목을 입력해주세요. + + +
+ {/* 리포트명 (한글) */} +
+ + setFormData({ ...formData, reportNameKor: e.target.value })} + /> +
+ + {/* 리포트명 (영문) */} +
+ + setFormData({ ...formData, reportNameEng: e.target.value })} + /> +
+ + {/* 템플릿 선택 */} +
+ + +
+ + {/* 리포트 타입 */} +
+ + +
+ + {/* 설명 */} +
+ +