167 lines
5.5 KiB
TypeScript
167 lines
5.5 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Trash2, Loader2, RefreshCw } from "lucide-react";
|
|
import {
|
|
AlertDialog,
|
|
AlertDialogAction,
|
|
AlertDialogCancel,
|
|
AlertDialogContent,
|
|
AlertDialogDescription,
|
|
AlertDialogFooter,
|
|
AlertDialogHeader,
|
|
AlertDialogTitle,
|
|
} from "@/components/ui/alert-dialog";
|
|
import { useReportDesigner } from "@/contexts/ReportDesignerContext";
|
|
import { reportApi } from "@/lib/api/reportApi";
|
|
import { useToast } from "@/hooks/use-toast";
|
|
|
|
interface Template {
|
|
template_id: string;
|
|
template_name_kor: string;
|
|
template_name_eng: string | null;
|
|
is_system: string;
|
|
}
|
|
|
|
export function TemplatePalette() {
|
|
const { applyTemplate } = useReportDesigner();
|
|
const [customTemplates, setCustomTemplates] = useState<Template[]>([]);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [deletingId, setDeletingId] = useState<string | null>(null);
|
|
const [deleteTarget, setDeleteTarget] = useState<{ id: string; name: string } | null>(null);
|
|
const { toast } = useToast();
|
|
|
|
const fetchTemplates = async () => {
|
|
setIsLoading(true);
|
|
try {
|
|
const response = await reportApi.getTemplates();
|
|
if (response.success && response.data) {
|
|
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 handleDeleteClick = (templateId: string, templateName: string) => {
|
|
setDeleteTarget({ id: templateId, name: templateName });
|
|
};
|
|
|
|
const handleDeleteConfirm = async () => {
|
|
if (!deleteTarget) return;
|
|
|
|
setDeletingId(deleteTarget.id);
|
|
setDeleteTarget(null);
|
|
|
|
try {
|
|
const response = await reportApi.deleteTemplate(deleteTarget.id);
|
|
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-2">
|
|
{/* 사용자 정의 템플릿 */}
|
|
<div className="space-y-2">
|
|
<div className="flex items-center justify-end">
|
|
<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();
|
|
handleDeleteClick(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>
|
|
|
|
{/* 삭제 확인 모달 */}
|
|
<AlertDialog open={!!deleteTarget} onOpenChange={(open) => !open && setDeleteTarget(null)}>
|
|
<AlertDialogContent className="max-w-[400px]">
|
|
<AlertDialogHeader>
|
|
<AlertDialogTitle>템플릿 삭제</AlertDialogTitle>
|
|
<AlertDialogDescription>
|
|
"{deleteTarget?.name}" 템플릿을 삭제하시겠습니까?
|
|
<br />
|
|
삭제된 템플릿은 복구할 수 없습니다.
|
|
</AlertDialogDescription>
|
|
</AlertDialogHeader>
|
|
<AlertDialogFooter>
|
|
<AlertDialogCancel>취소</AlertDialogCancel>
|
|
<AlertDialogAction
|
|
onClick={handleDeleteConfirm}
|
|
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
>
|
|
삭제
|
|
</AlertDialogAction>
|
|
</AlertDialogFooter>
|
|
</AlertDialogContent>
|
|
</AlertDialog>
|
|
</div>
|
|
);
|
|
}
|