리포트 저장 구현

This commit is contained in:
dohyeons
2025-10-01 14:23:00 +09:00
parent de97c40517
commit dfac694e4d
5 changed files with 69 additions and 38 deletions

View File

@@ -47,6 +47,8 @@ export function CanvasComponent({ component }: CanvasComponentProps) {
// 마우스 이동 핸들러 (전역)
useEffect(() => {
if (!isDragging && !isResizing) return;
const handleMouseMove = (e: MouseEvent) => {
if (isDragging) {
const newX = Math.max(0, e.clientX - dragStart.x);
@@ -66,15 +68,25 @@ export function CanvasComponent({ component }: CanvasComponentProps) {
setIsResizing(false);
};
if (isDragging || isResizing) {
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
return () => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
}
}, [isDragging, isResizing, dragStart, resizeStart, component.id, updateComponent]);
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
return () => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
}, [
isDragging,
isResizing,
dragStart.x,
dragStart.y,
resizeStart.x,
resizeStart.y,
resizeStart.width,
resizeStart.height,
component.id,
updateComponent,
]);
// 표시할 값 결정
const getDisplayValue = (): string => {

View File

@@ -67,7 +67,6 @@ export function QueryManager() {
setSelectedQueryId(null);
setParameterValues({});
setParameterTypes({});
setTestResult(null);
}
};
@@ -76,7 +75,6 @@ export function QueryManager() {
setSelectedQueryId(queryId);
setParameterValues({});
setParameterTypes({});
setTestResult(null);
};
// 파라미터 값이 모두 입력되었는지 확인

View File

@@ -38,9 +38,9 @@ export function ReportDesignerCanvas() {
fontFamily: "Malgun Gothic",
fontWeight: "normal",
fontColor: "#000000",
backgroundColor: "#ffffff",
borderWidth: 1,
borderColor: "#666666",
backgroundColor: "transparent",
borderWidth: 0,
borderColor: "#cccccc",
borderRadius: 5,
textAlign: "left",
padding: 10,

View File

@@ -16,6 +16,11 @@ export function ReportDesignerToolbar() {
await saveLayout();
};
const handleSaveAndClose = async () => {
await saveLayout();
router.push("/admin/report");
};
const handleReset = async () => {
if (confirm("현재 변경사항을 모두 취소하고 마지막 저장 상태로 되돌리시겠습니까?")) {
await loadLayout();
@@ -56,7 +61,7 @@ export function ReportDesignerToolbar() {
<Eye className="h-4 w-4" />
</Button>
<Button size="sm" onClick={handleSave} disabled={isSaving} className="gap-2">
<Button variant="outline" size="sm" onClick={handleSave} disabled={isSaving} className="gap-2">
{isSaving ? (
<>
<Loader2 className="h-4 w-4 animate-spin" />
@@ -69,6 +74,19 @@ export function ReportDesignerToolbar() {
</>
)}
</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>

View File

@@ -33,8 +33,8 @@ function getTemplateLayout(templateId: string): TemplateLayout | null {
height: 40,
fontSize: 24,
fontColor: "#000000",
backgroundColor: "#ffffff",
borderColor: "#000000",
backgroundColor: "transparent",
borderColor: "#cccccc",
borderWidth: 0,
zIndex: 1,
defaultValue: "발주서",
@@ -48,9 +48,9 @@ function getTemplateLayout(templateId: string): TemplateLayout | null {
height: 30,
fontSize: 14,
fontColor: "#000000",
backgroundColor: "#ffffff",
backgroundColor: "transparent",
borderColor: "#cccccc",
borderWidth: 1,
borderWidth: 0,
zIndex: 1,
},
{
@@ -62,9 +62,9 @@ function getTemplateLayout(templateId: string): TemplateLayout | null {
height: 30,
fontSize: 14,
fontColor: "#000000",
backgroundColor: "#ffffff",
backgroundColor: "transparent",
borderColor: "#cccccc",
borderWidth: 1,
borderWidth: 0,
zIndex: 1,
},
{
@@ -76,9 +76,9 @@ function getTemplateLayout(templateId: string): TemplateLayout | null {
height: 30,
fontSize: 14,
fontColor: "#000000",
backgroundColor: "#ffffff",
backgroundColor: "transparent",
borderColor: "#cccccc",
borderWidth: 1,
borderWidth: 0,
zIndex: 1,
},
{
@@ -90,9 +90,9 @@ function getTemplateLayout(templateId: string): TemplateLayout | null {
height: 200,
fontSize: 12,
fontColor: "#000000",
backgroundColor: "#ffffff",
backgroundColor: "transparent",
borderColor: "#cccccc",
borderWidth: 1,
borderWidth: 0,
zIndex: 1,
},
],
@@ -126,8 +126,8 @@ function getTemplateLayout(templateId: string): TemplateLayout | null {
height: 40,
fontSize: 24,
fontColor: "#000000",
backgroundColor: "#ffffff",
borderColor: "#000000",
backgroundColor: "transparent",
borderColor: "#cccccc",
borderWidth: 0,
zIndex: 1,
defaultValue: "청구서",
@@ -141,9 +141,9 @@ function getTemplateLayout(templateId: string): TemplateLayout | null {
height: 30,
fontSize: 14,
fontColor: "#000000",
backgroundColor: "#ffffff",
backgroundColor: "transparent",
borderColor: "#cccccc",
borderWidth: 1,
borderWidth: 0,
zIndex: 1,
},
{
@@ -155,9 +155,9 @@ function getTemplateLayout(templateId: string): TemplateLayout | null {
height: 30,
fontSize: 14,
fontColor: "#000000",
backgroundColor: "#ffffff",
backgroundColor: "transparent",
borderColor: "#cccccc",
borderWidth: 1,
borderWidth: 0,
zIndex: 1,
},
{
@@ -169,9 +169,9 @@ function getTemplateLayout(templateId: string): TemplateLayout | null {
height: 200,
fontSize: 12,
fontColor: "#000000",
backgroundColor: "#ffffff",
backgroundColor: "transparent",
borderColor: "#cccccc",
borderWidth: 1,
borderWidth: 0,
zIndex: 1,
},
{
@@ -220,8 +220,8 @@ function getTemplateLayout(templateId: string): TemplateLayout | null {
height: 40,
fontSize: 20,
fontColor: "#000000",
backgroundColor: "#ffffff",
borderColor: "#000000",
backgroundColor: "transparent",
borderColor: "#cccccc",
borderWidth: 0,
zIndex: 1,
defaultValue: "리포트 제목",
@@ -235,9 +235,9 @@ function getTemplateLayout(templateId: string): TemplateLayout | null {
height: 100,
fontSize: 14,
fontColor: "#000000",
backgroundColor: "#ffffff",
backgroundColor: "transparent",
borderColor: "#cccccc",
borderWidth: 1,
borderWidth: 0,
zIndex: 1,
defaultValue: "내용을 입력하세요",
},
@@ -381,6 +381,8 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin
// 레이아웃이 없으면 기본값 사용
console.log("레이아웃 없음, 기본값 사용");
}
// 쿼리 조회는 이미 위에서 처리됨 (reportResponse.data.queries)
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "리포트를 불러오는데 실패했습니다.";
toast({
@@ -396,7 +398,8 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin
// 초기 로드
useEffect(() => {
loadLayout();
}, [loadLayout]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [reportId]);
// 쿼리 결과 저장
const setQueryResult = useCallback((queryId: string, fields: string[], rows: Record<string, unknown>[]) => {