레이어 관리 구현
This commit is contained in:
parent
722a413916
commit
172ecf34b3
|
|
@ -22,6 +22,10 @@ import {
|
||||||
RectangleHorizontal,
|
RectangleHorizontal,
|
||||||
RectangleVertical,
|
RectangleVertical,
|
||||||
Square,
|
Square,
|
||||||
|
ChevronDown,
|
||||||
|
ChevronsDown,
|
||||||
|
ChevronsUp,
|
||||||
|
ChevronUp,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useReportDesigner } from "@/contexts/ReportDesignerContext";
|
import { useReportDesigner } from "@/contexts/ReportDesignerContext";
|
||||||
|
|
@ -70,14 +74,19 @@ export function ReportDesignerToolbar() {
|
||||||
makeSameWidth,
|
makeSameWidth,
|
||||||
makeSameHeight,
|
makeSameHeight,
|
||||||
makeSameSize,
|
makeSameSize,
|
||||||
|
bringToFront,
|
||||||
|
sendToBack,
|
||||||
|
bringForward,
|
||||||
|
sendBackward,
|
||||||
} = useReportDesigner();
|
} = useReportDesigner();
|
||||||
const [showPreview, setShowPreview] = useState(false);
|
const [showPreview, setShowPreview] = useState(false);
|
||||||
const [showSaveAsTemplate, setShowSaveAsTemplate] = useState(false);
|
const [showSaveAsTemplate, setShowSaveAsTemplate] = useState(false);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
// 정렬 버튼 활성화 조건
|
// 버튼 활성화 조건
|
||||||
const canAlign = selectedComponentIds && selectedComponentIds.length >= 2;
|
const canAlign = selectedComponentIds && selectedComponentIds.length >= 2;
|
||||||
const canDistribute = selectedComponentIds && selectedComponentIds.length >= 3;
|
const canDistribute = selectedComponentIds && selectedComponentIds.length >= 3;
|
||||||
|
const hasSelection = selectedComponentIds && selectedComponentIds.length >= 1;
|
||||||
|
|
||||||
// 템플릿 저장 가능 여부: 컴포넌트가 있어야 함
|
// 템플릿 저장 가능 여부: 컴포넌트가 있어야 함
|
||||||
const canSaveAsTemplate = components.length > 0;
|
const canSaveAsTemplate = components.length > 0;
|
||||||
|
|
@ -316,6 +325,38 @@ export function ReportDesignerToolbar() {
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|
||||||
|
{/* 레이어 드롭다운 */}
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
disabled={!hasSelection}
|
||||||
|
className="gap-2"
|
||||||
|
title="레이어 순서 (1개 이상 선택 필요)"
|
||||||
|
>
|
||||||
|
<ChevronsUp className="h-4 w-4" />
|
||||||
|
레이어
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem onClick={bringToFront}>
|
||||||
|
<ChevronsUp className="mr-2 h-4 w-4" />맨 앞으로
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={bringForward}>
|
||||||
|
<ChevronUp className="mr-2 h-4 w-4" />한 단계 앞으로
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem onClick={sendBackward}>
|
||||||
|
<ChevronDown className="mr-2 h-4 w-4" />한 단계 뒤로
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={sendToBack}>
|
||||||
|
<ChevronsDown className="mr-2 h-4 w-4" />맨 뒤로
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
|
||||||
<Button variant="outline" size="sm" onClick={handleReset} className="gap-2">
|
<Button variant="outline" size="sm" onClick={handleReset} className="gap-2">
|
||||||
<RotateCcw className="h-4 w-4" />
|
<RotateCcw className="h-4 w-4" />
|
||||||
초기화
|
초기화
|
||||||
|
|
|
||||||
|
|
@ -345,6 +345,12 @@ interface ReportDesignerContextType {
|
||||||
makeSameWidth: () => void;
|
makeSameWidth: () => void;
|
||||||
makeSameHeight: () => void;
|
makeSameHeight: () => void;
|
||||||
makeSameSize: () => void;
|
makeSameSize: () => void;
|
||||||
|
|
||||||
|
// 레이어 관리
|
||||||
|
bringToFront: () => void;
|
||||||
|
sendToBack: () => void;
|
||||||
|
bringForward: () => void;
|
||||||
|
sendBackward: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReportDesignerContext = createContext<ReportDesignerContextType | undefined>(undefined);
|
const ReportDesignerContext = createContext<ReportDesignerContextType | undefined>(undefined);
|
||||||
|
|
@ -769,6 +775,87 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin
|
||||||
toast({ title: "크기 조정 완료", description: "같은 크기로 조정되었습니다." });
|
toast({ title: "크기 조정 완료", description: "같은 크기로 조정되었습니다." });
|
||||||
}, [getSelectedComponents, toast]);
|
}, [getSelectedComponents, toast]);
|
||||||
|
|
||||||
|
// 레이어 관리 함수들
|
||||||
|
const bringToFront = useCallback(() => {
|
||||||
|
if (!selectedComponentId && selectedComponentIds.length === 0) return;
|
||||||
|
|
||||||
|
const idsToUpdate =
|
||||||
|
selectedComponentIds.length > 0 ? selectedComponentIds : ([selectedComponentId].filter(Boolean) as string[]);
|
||||||
|
const maxZIndex = Math.max(...components.map((c) => c.zIndex));
|
||||||
|
|
||||||
|
setComponents((prev) =>
|
||||||
|
prev.map((c) => {
|
||||||
|
if (idsToUpdate.includes(c.id)) {
|
||||||
|
return { ...c, zIndex: maxZIndex + 1 };
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
toast({ title: "레이어 변경", description: "맨 앞으로 이동했습니다." });
|
||||||
|
}, [selectedComponentId, selectedComponentIds, components, toast]);
|
||||||
|
|
||||||
|
const sendToBack = useCallback(() => {
|
||||||
|
if (!selectedComponentId && selectedComponentIds.length === 0) return;
|
||||||
|
|
||||||
|
const idsToUpdate =
|
||||||
|
selectedComponentIds.length > 0 ? selectedComponentIds : ([selectedComponentId].filter(Boolean) as string[]);
|
||||||
|
const minZIndex = Math.min(...components.map((c) => c.zIndex));
|
||||||
|
|
||||||
|
setComponents((prev) =>
|
||||||
|
prev.map((c) => {
|
||||||
|
if (idsToUpdate.includes(c.id)) {
|
||||||
|
return { ...c, zIndex: minZIndex - 1 };
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
toast({ title: "레이어 변경", description: "맨 뒤로 이동했습니다." });
|
||||||
|
}, [selectedComponentId, selectedComponentIds, components, toast]);
|
||||||
|
|
||||||
|
const bringForward = useCallback(() => {
|
||||||
|
if (!selectedComponentId && selectedComponentIds.length === 0) return;
|
||||||
|
|
||||||
|
const idsToUpdate =
|
||||||
|
selectedComponentIds.length > 0 ? selectedComponentIds : ([selectedComponentId].filter(Boolean) as string[]);
|
||||||
|
|
||||||
|
setComponents((prev) => {
|
||||||
|
const sorted = [...prev].sort((a, b) => a.zIndex - b.zIndex);
|
||||||
|
const updated = sorted.map((c, index) => ({ ...c, zIndex: index }));
|
||||||
|
|
||||||
|
return updated.map((c) => {
|
||||||
|
if (idsToUpdate.includes(c.id)) {
|
||||||
|
return { ...c, zIndex: Math.min(c.zIndex + 1, updated.length - 1) };
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
toast({ title: "레이어 변경", description: "한 단계 앞으로 이동했습니다." });
|
||||||
|
}, [selectedComponentId, selectedComponentIds, toast]);
|
||||||
|
|
||||||
|
const sendBackward = useCallback(() => {
|
||||||
|
if (!selectedComponentId && selectedComponentIds.length === 0) return;
|
||||||
|
|
||||||
|
const idsToUpdate =
|
||||||
|
selectedComponentIds.length > 0 ? selectedComponentIds : ([selectedComponentId].filter(Boolean) as string[]);
|
||||||
|
|
||||||
|
setComponents((prev) => {
|
||||||
|
const sorted = [...prev].sort((a, b) => a.zIndex - b.zIndex);
|
||||||
|
const updated = sorted.map((c, index) => ({ ...c, zIndex: index }));
|
||||||
|
|
||||||
|
return updated.map((c) => {
|
||||||
|
if (idsToUpdate.includes(c.id)) {
|
||||||
|
return { ...c, zIndex: Math.max(c.zIndex - 1, 0) };
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
toast({ title: "레이어 변경", description: "한 단계 뒤로 이동했습니다." });
|
||||||
|
}, [selectedComponentId, selectedComponentIds, toast]);
|
||||||
|
|
||||||
// 캔버스 설정 (기본값)
|
// 캔버스 설정 (기본값)
|
||||||
const [canvasWidth, setCanvasWidth] = useState(210);
|
const [canvasWidth, setCanvasWidth] = useState(210);
|
||||||
const [canvasHeight, setCanvasHeight] = useState(297);
|
const [canvasHeight, setCanvasHeight] = useState(297);
|
||||||
|
|
@ -1180,6 +1267,11 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin
|
||||||
makeSameWidth,
|
makeSameWidth,
|
||||||
makeSameHeight,
|
makeSameHeight,
|
||||||
makeSameSize,
|
makeSameSize,
|
||||||
|
// 레이어 관리
|
||||||
|
bringToFront,
|
||||||
|
sendToBack,
|
||||||
|
bringForward,
|
||||||
|
sendBackward,
|
||||||
};
|
};
|
||||||
|
|
||||||
return <ReportDesignerContext.Provider value={value}>{children}</ReportDesignerContext.Provider>;
|
return <ReportDesignerContext.Provider value={value}>{children}</ReportDesignerContext.Provider>;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue