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

209 lines
6.6 KiB
TypeScript

"use client";
import { Button } from "@/components/ui/button";
import { Save, Eye, RotateCcw, ArrowLeft, Loader2, BookTemplate, Grid3x3 } from "lucide-react";
import { useRouter } from "next/navigation";
import { useReportDesigner } from "@/contexts/ReportDesignerContext";
import { useState } from "react";
import { SaveAsTemplateModal } from "./SaveAsTemplateModal";
import { reportApi } from "@/lib/api/reportApi";
import { useToast } from "@/hooks/use-toast";
import { ReportPreviewModal } from "./ReportPreviewModal";
export function ReportDesignerToolbar() {
const router = useRouter();
const {
reportDetail,
saveLayout,
isSaving,
loadLayout,
components,
canvasWidth,
canvasHeight,
queries,
snapToGrid,
setSnapToGrid,
showGrid,
setShowGrid,
} = useReportDesigner();
const [showPreview, setShowPreview] = useState(false);
const [showSaveAsTemplate, setShowSaveAsTemplate] = useState(false);
const { toast } = useToast();
// 템플릿 저장 가능 여부: 컴포넌트가 있어야 함
const canSaveAsTemplate = components.length > 0;
// Grid 토글 (Snap과 Grid 표시 함께 제어)
const handleToggleGrid = () => {
const newValue = !snapToGrid;
setSnapToGrid(newValue);
setShowGrid(newValue);
};
const handleSave = async () => {
await saveLayout();
};
const handleSaveAndClose = async () => {
await saveLayout();
router.push("/admin/report");
};
const handleReset = async () => {
if (confirm("현재 변경사항을 모두 취소하고 마지막 저장 상태로 되돌리시겠습니까?")) {
await loadLayout();
}
};
const handleBack = () => {
if (confirm("저장하지 않은 변경사항이 있을 수 있습니다. 목록으로 돌아가시겠습니까?")) {
router.push("/admin/report");
}
};
const handleSaveAsTemplate = async (data: {
templateNameKor: string;
templateNameEng?: string;
description?: string;
}) => {
try {
// 현재 레이아웃 데이터로 직접 템플릿 생성 (리포트 저장 불필요)
const response = await reportApi.createTemplateFromLayout({
templateNameKor: data.templateNameKor,
templateNameEng: data.templateNameEng,
templateType: reportDetail?.report?.report_type || "GENERAL",
description: data.description,
layoutConfig: {
width: canvasWidth,
height: canvasHeight,
orientation: "portrait",
margins: {
top: 10,
bottom: 10,
left: 10,
right: 10,
},
components: components,
},
defaultQueries: queries.map((q, index) => ({
name: q.name,
type: q.type,
sqlQuery: q.sqlQuery,
parameters: q.parameters,
externalConnectionId: q.externalConnectionId || null,
displayOrder: index,
})),
});
if (response.success) {
toast({
title: "성공",
description: "템플릿이 생성되었습니다.",
});
setShowSaveAsTemplate(false);
}
} catch (error: unknown) {
const errorMessage =
error instanceof Error && "response" in error
? (error as { response?: { data?: { message?: string } } }).response?.data?.message ||
"템플릿 생성에 실패했습니다."
: "템플릿 생성에 실패했습니다.";
toast({
title: "오류",
description: errorMessage,
variant: "destructive",
});
throw error;
}
};
return (
<>
<div className="flex items-center justify-between border-b bg-white px-4 py-3 shadow-sm">
<div className="flex items-center gap-4">
<Button variant="ghost" size="sm" onClick={handleBack} className="gap-2">
<ArrowLeft className="h-4 w-4" />
</Button>
<div className="h-6 w-px bg-gray-300" />
<div>
<h2 className="text-lg font-semibold text-gray-900">
{reportDetail?.report.report_name_kor || "리포트 디자이너"}
</h2>
{reportDetail?.report.report_name_eng && (
<p className="text-sm text-gray-500">{reportDetail.report.report_name_eng}</p>
)}
</div>
</div>
<div className="flex items-center gap-2">
<Button
variant={snapToGrid && showGrid ? "default" : "outline"}
size="sm"
onClick={handleToggleGrid}
className="gap-2"
title="Grid Snap 및 표시 켜기/끄기"
>
<Grid3x3 className="h-4 w-4" />
{snapToGrid && showGrid ? "Grid ON" : "Grid OFF"}
</Button>
<Button variant="outline" size="sm" onClick={handleReset} className="gap-2">
<RotateCcw className="h-4 w-4" />
</Button>
<Button variant="outline" size="sm" onClick={() => setShowPreview(true)} className="gap-2">
<Eye className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => setShowSaveAsTemplate(true)}
disabled={!canSaveAsTemplate}
className="gap-2"
title={!canSaveAsTemplate ? "컴포넌트를 추가한 후 템플릿으로 저장할 수 있습니다" : ""}
>
<BookTemplate className="h-4 w-4" />
릿
</Button>
<Button variant="outline" size="sm" onClick={handleSave} disabled={isSaving} className="gap-2">
{isSaving ? (
<>
<Loader2 className="h-4 w-4 animate-spin" />
...
</>
) : (
<>
<Save className="h-4 w-4" />
</>
)}
</Button>
<Button size="sm" onClick={handleSaveAndClose} disabled={isSaving} className="gap-2">
{isSaving ? (
<>
<Loader2 className="h-4 w-4 animate-spin" />
...
</>
) : (
<>
<Save className="h-4 w-4" />
</>
)}
</Button>
</div>
</div>
<ReportPreviewModal isOpen={showPreview} onClose={() => setShowPreview(false)} />
<SaveAsTemplateModal
isOpen={showSaveAsTemplate}
onClose={() => setShowSaveAsTemplate(false)}
onSave={handleSaveAsTemplate}
/>
</>
);
}