ERP-node/frontend/components/report/designer/TemplatePalette.tsx

153 lines
5.2 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import { Trash2, Loader2, RefreshCw } from "lucide-react";
import { useReportDesigner } from "@/contexts/ReportDesignerContext";
import { reportApi } from "@/lib/api/reportApi";
import { useToast } from "@/hooks/use-toast";
import { Badge } from "@/components/ui/badge";
interface Template {
template_id: string;
template_name_kor: string;
template_name_eng: string | null;
is_system: string;
}
export function TemplatePalette() {
const { applyTemplate } = useReportDesigner();
const [systemTemplates, setSystemTemplates] = useState<Template[]>([]);
const [customTemplates, setCustomTemplates] = useState<Template[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [deletingId, setDeletingId] = useState<string | null>(null);
const { toast } = useToast();
const fetchTemplates = async () => {
setIsLoading(true);
try {
const response = await reportApi.getTemplates();
if (response.success && response.data) {
setSystemTemplates(Array.isArray(response.data.system) ? response.data.system : []);
setCustomTemplates(Array.isArray(response.data.custom) ? response.data.custom : []);
}
} catch (error) {
console.error("템플릿 조회 실패:", error);
toast({
title: "오류",
description: "템플릿 목록을 불러올 수 없습니다.",
variant: "destructive",
});
} finally {
setIsLoading(false);
}
};
useEffect(() => {
fetchTemplates();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleApplyTemplate = async (templateId: string) => {
await applyTemplate(templateId);
};
const handleDeleteTemplate = async (templateId: string, templateName: string) => {
if (!confirm(`"${templateName}" 템플릿을 삭제하시겠습니까?`)) {
return;
}
setDeletingId(templateId);
try {
const response = await reportApi.deleteTemplate(templateId);
if (response.success) {
toast({
title: "성공",
description: "템플릿이 삭제되었습니다.",
});
fetchTemplates();
}
} catch (error: any) {
toast({
title: "오류",
description: error.response?.data?.message || "템플릿 삭제에 실패했습니다.",
variant: "destructive",
});
} finally {
setDeletingId(null);
}
};
return (
<div className="space-y-4">
{/* 시스템 템플릿 (DB에서 조회) */}
{systemTemplates.length > 0 && (
<div className="space-y-2">
<div className="flex items-center justify-between">
<p className="text-xs font-semibold text-gray-600"> 릿</p>
</div>
{systemTemplates.map((template) => (
<Button
key={template.template_id}
variant="outline"
size="sm"
className="w-full justify-start gap-2 text-sm"
onClick={() => handleApplyTemplate(template.template_id)}
>
<span>{template.template_name_kor}</span>
</Button>
))}
</div>
)}
{/* 사용자 정의 템플릿 */}
<div className="space-y-2">
<div className="flex items-center justify-between">
<p className="text-xs font-semibold text-gray-600"> 릿</p>
<Button variant="ghost" size="sm" onClick={fetchTemplates} disabled={isLoading} className="h-6 w-6 p-0">
<RefreshCw className={`h-3 w-3 ${isLoading ? "animate-spin" : ""}`} />
</Button>
</div>
{isLoading ? (
<div className="flex items-center justify-center py-4">
<Loader2 className="h-4 w-4 animate-spin text-gray-400" />
</div>
) : customTemplates.length === 0 ? (
<p className="py-4 text-center text-xs text-gray-400"> 릿 </p>
) : (
customTemplates.map((template) => (
<div key={template.template_id} className="group relative">
<Button
variant="outline"
size="sm"
className="w-full justify-start gap-2 pr-8 text-sm"
onClick={() => handleApplyTemplate(template.template_id)}
>
<span>📄</span>
<span className="truncate">{template.template_name_kor}</span>
</Button>
<Button
variant="ghost"
size="sm"
onClick={(e) => {
e.stopPropagation();
handleDeleteTemplate(template.template_id, template.template_name_kor);
}}
disabled={deletingId === template.template_id}
className="absolute top-1/2 right-1 h-6 w-6 -translate-y-1/2 p-0 opacity-0 transition-opacity group-hover:opacity-100"
>
{deletingId === template.template_id ? (
<Loader2 className="h-3 w-3 animate-spin" />
) : (
<Trash2 className="h-3 w-3 text-red-500" />
)}
</Button>
</div>
))
)}
</div>
</div>
);
}