"use client"; import { useState, useEffect, useMemo, useCallback } from "react"; import { useRouter } from "next/navigation"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { useUnsavedChangesGuard, UnsavedChangesDialog } from "@/components/common/UnsavedChangesGuard"; 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 { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "@/components/ui/command"; import { Loader2, LayoutTemplate, Check, ChevronsUpDown, Plus, Tag } from "lucide-react"; import { reportApi } from "@/lib/api/reportApi"; import { useToast } from "@/hooks/use-toast"; import { REPORT_TYPE_OPTIONS, getTypeIcon, getTypeLabel } from "@/lib/reportTypeColors"; import { ReportTemplate } from "@/types/report"; import { cn } from "@/lib/utils"; interface ReportCreateModalProps { isOpen: boolean; onClose: () => void; onSuccess: () => void; } const TEMPLATE_NONE = "__none__"; export function ReportCreateModal({ isOpen, onClose, onSuccess }: ReportCreateModalProps) { const router = useRouter(); const [reportName, setReportName] = useState(""); const [reportType, setReportType] = useState(""); const [customCategory, setCustomCategory] = useState(""); const [categoryOpen, setCategoryOpen] = useState(false); const [description, setDescription] = useState(""); const [selectedTemplateId, setSelectedTemplateId] = useState(TEMPLATE_NONE); const [isLoading, setIsLoading] = useState(false); const [isLoadingTemplates, setIsLoadingTemplates] = useState(false); const [isLoadingCategories, setIsLoadingCategories] = useState(false); const [systemTemplates, setSystemTemplates] = useState([]); const [customTemplates, setCustomTemplates] = useState([]); const [existingCategories, setExistingCategories] = useState([]); const { toast } = useToast(); useEffect(() => { if (!isOpen) return; const fetchTemplates = async () => { setIsLoadingTemplates(true); try { const response = await reportApi.getTemplates(); if (response.success && response.data) { setSystemTemplates(response.data.system || []); setCustomTemplates(response.data.custom || []); } } catch { // 템플릿 로딩 실패 시 빈 목록으로 진행 } finally { setIsLoadingTemplates(false); } }; const fetchCategories = async () => { setIsLoadingCategories(true); try { const response = await reportApi.getCategories(); if (response.success && response.data) { setExistingCategories(response.data); } } catch { // 카테고리 로딩 실패 시 빈 목록으로 진행 } finally { setIsLoadingCategories(false); } }; fetchTemplates(); fetchCategories(); }, [isOpen]); const hasTemplates = useMemo( () => systemTemplates.length > 0 || customTemplates.length > 0, [systemTemplates, customTemplates], ); const allCategories = useMemo(() => { const defaultTypes = REPORT_TYPE_OPTIONS.map((opt) => opt.value); const merged = new Set([...defaultTypes, ...existingCategories]); return Array.from(merged).sort(); }, [existingCategories]); const effectiveCategory = useMemo(() => { return customCategory.trim() || reportType; }, [customCategory, reportType]); const categoryDisplayLabel = useMemo(() => { if (customCategory.trim()) return customCategory.trim(); if (reportType) return getTypeLabel(reportType); return ""; }, [customCategory, reportType]); const hasInputData = useCallback(() => { return reportName.trim() !== "" || reportType !== "" || customCategory.trim() !== "" || description.trim() !== "" || selectedTemplateId !== TEMPLATE_NONE; }, [reportName, reportType, customCategory, description, selectedTemplateId]); const resetForm = useCallback(() => { setReportName(""); setReportType(""); setCustomCategory(""); setDescription(""); setSelectedTemplateId(TEMPLATE_NONE); }, []); const guard = useUnsavedChangesGuard({ hasChanges: () => !isLoading && hasInputData(), onClose: () => { resetForm(); onClose(); }, title: "입력된 내용이 있습니다", description: "입력된 내용이 저장되지 않습니다. 정말 닫으시겠습니까?", }); const handleCategorySelect = (value: string) => { setReportType(value); setCustomCategory(""); setCategoryOpen(false); }; const handleCustomCategoryAdd = () => { const trimmed = customCategory.trim(); if (trimmed) { setReportType(""); setCategoryOpen(false); } }; const handleSubmit = async () => { const trimmed = reportName.trim(); if (!trimmed) { toast({ title: "입력 오류", description: "리포트명을 입력해주세요.", variant: "destructive", }); return; } const finalCategory = effectiveCategory; if (!finalCategory) { toast({ title: "입력 오류", description: "카테고리를 선택하거나 입력해주세요.", variant: "destructive", }); return; } setIsLoading(true); try { const response = await reportApi.createReport({ reportNameKor: trimmed, reportType: finalCategory, description: description.trim() || undefined, templateId: selectedTemplateId !== TEMPLATE_NONE ? selectedTemplateId : undefined, }); if (response.success && response.data) { toast({ title: "성공", description: "리포트가 생성되었습니다." }); guard.doClose(); onSuccess(); router.push(`/admin/screenMng/reportList/designer/${response.data.reportId}`); } } catch (error: unknown) { const msg = error instanceof Error ? error.message : "리포트 생성에 실패했습니다."; toast({ title: "오류", description: msg, variant: "destructive" }); } finally { setIsLoading(false); } }; return ( <> 새 리포트 생성 리포트명과 카테고리를 입력한 후 디자이너에서 상세 설계를 진행합니다.
setReportName(e.target.value)} onKeyDown={(e) => e.key === "Enter" && !isLoading && handleSubmit()} className="h-11 text-base" autoFocus />
{customCategory.trim() && !allCategories.includes(customCategory.trim()) && ( "{customCategory.trim()}" 새로 추가 )} 일치하는 카테고리가 없습니다.
위에 입력한 값으로 새 카테고리를 추가할 수 있습니다.
{allCategories.map((cat) => { const Icon = getTypeIcon(cat); const label = getTypeLabel(cat); return ( handleCategorySelect(cat)} className="text-base" > {label} {cat !== label && ( ({cat}) )} ); })}

기존 카테고리를 선택하거나 새로운 카테고리를 직접 입력할 수 있습니다.

템플릿을 선택하면 레이아웃이 자동으로 적용됩니다.