워터마크를 전체 페이지 공유 방식으로 변경
This commit is contained in:
parent
d7f015b37d
commit
5f26e998e3
|
|
@ -3067,8 +3067,8 @@ export class ReportController {
|
|||
children.push(new Paragraph({ children: [] }));
|
||||
}
|
||||
|
||||
// 워터마크 헤더 생성 (워터마크가 활성화된 경우)
|
||||
const watermark: WatermarkConfig | undefined = page.watermark;
|
||||
// 워터마크 헤더 생성 (전체 페이지 공유 워터마크)
|
||||
const watermark: WatermarkConfig | undefined = layoutConfig.watermark;
|
||||
let headers: { default?: Header } | undefined;
|
||||
|
||||
if (watermark?.enabled && watermark.type === "text" && watermark.text) {
|
||||
|
|
|
|||
|
|
@ -147,12 +147,12 @@ export interface PageConfig {
|
|||
right: number;
|
||||
};
|
||||
components: any[];
|
||||
watermark?: WatermarkConfig;
|
||||
}
|
||||
|
||||
// 레이아웃 설정
|
||||
export interface ReportLayoutConfig {
|
||||
pages: PageConfig[];
|
||||
watermark?: WatermarkConfig; // 전체 페이지 공유 워터마크
|
||||
}
|
||||
|
||||
// 레이아웃 저장 요청
|
||||
|
|
|
|||
|
|
@ -213,6 +213,7 @@ export function ReportDesignerCanvas() {
|
|||
undo,
|
||||
redo,
|
||||
showRuler,
|
||||
layoutConfig,
|
||||
} = useReportDesigner();
|
||||
|
||||
const [{ isOver }, drop] = useDrop(() => ({
|
||||
|
|
@ -608,10 +609,10 @@ export function ReportDesignerCanvas() {
|
|||
/>
|
||||
)}
|
||||
|
||||
{/* 워터마크 렌더링 */}
|
||||
{currentPage?.watermark?.enabled && (
|
||||
{/* 워터마크 렌더링 (전체 페이지 공유) */}
|
||||
{layoutConfig.watermark?.enabled && (
|
||||
<WatermarkLayer
|
||||
watermark={currentPage.watermark}
|
||||
watermark={layoutConfig.watermark}
|
||||
canvasWidth={canvasWidth * MM_TO_PX}
|
||||
canvasHeight={canvasHeight * MM_TO_PX}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ export function ReportDesignerRightPanel() {
|
|||
currentPageId,
|
||||
updatePageSettings,
|
||||
getQueryResult,
|
||||
layoutConfig,
|
||||
updateWatermark,
|
||||
} = context;
|
||||
const [activeTab, setActiveTab] = useState<string>("properties");
|
||||
const [uploadingImage, setUploadingImage] = useState(false);
|
||||
|
|
@ -101,7 +103,7 @@ export function ReportDesignerRightPanel() {
|
|||
// 워터마크 이미지 업로드 핸들러
|
||||
const handleWatermarkImageUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file || !currentPageId) return;
|
||||
if (!file) return;
|
||||
|
||||
// 파일 타입 체크
|
||||
if (!file.type.startsWith("image/")) {
|
||||
|
|
@ -129,12 +131,10 @@ export function ReportDesignerRightPanel() {
|
|||
const result = await reportApi.uploadImage(file);
|
||||
|
||||
if (result.success) {
|
||||
// 업로드된 이미지 URL을 워터마크에 설정
|
||||
updatePageSettings(currentPageId, {
|
||||
watermark: {
|
||||
...currentPage!.watermark!,
|
||||
imageUrl: result.data.fileUrl,
|
||||
},
|
||||
// 업로드된 이미지 URL을 전체 워터마크에 설정
|
||||
updateWatermark({
|
||||
...layoutConfig.watermark!,
|
||||
imageUrl: result.data.fileUrl,
|
||||
});
|
||||
|
||||
toast({
|
||||
|
|
@ -2690,44 +2690,40 @@ export function ReportDesignerRightPanel() {
|
|||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 워터마크 설정 */}
|
||||
{/* 워터마크 설정 (전체 페이지 공유) */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-sm">워터마크</CardTitle>
|
||||
<CardTitle className="text-sm">워터마크 (전체 페이지)</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{/* 워터마크 활성화 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-xs">워터마크 사용</Label>
|
||||
<Switch
|
||||
checked={currentPage.watermark?.enabled ?? false}
|
||||
checked={layoutConfig.watermark?.enabled ?? false}
|
||||
onCheckedChange={(checked) =>
|
||||
updatePageSettings(currentPageId, {
|
||||
watermark: {
|
||||
...currentPage.watermark,
|
||||
enabled: checked,
|
||||
type: currentPage.watermark?.type ?? "text",
|
||||
opacity: currentPage.watermark?.opacity ?? 0.3,
|
||||
style: currentPage.watermark?.style ?? "diagonal",
|
||||
},
|
||||
updateWatermark({
|
||||
...layoutConfig.watermark,
|
||||
enabled: checked,
|
||||
type: layoutConfig.watermark?.type ?? "text",
|
||||
opacity: layoutConfig.watermark?.opacity ?? 0.3,
|
||||
style: layoutConfig.watermark?.style ?? "diagonal",
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{currentPage.watermark?.enabled && (
|
||||
{layoutConfig.watermark?.enabled && (
|
||||
<>
|
||||
{/* 워터마크 타입 */}
|
||||
<div>
|
||||
<Label className="text-xs">타입</Label>
|
||||
<Select
|
||||
value={currentPage.watermark?.type ?? "text"}
|
||||
value={layoutConfig.watermark?.type ?? "text"}
|
||||
onValueChange={(value: "text" | "image") =>
|
||||
updatePageSettings(currentPageId, {
|
||||
watermark: {
|
||||
...currentPage.watermark!,
|
||||
type: value,
|
||||
},
|
||||
updateWatermark({
|
||||
...layoutConfig.watermark!,
|
||||
type: value,
|
||||
})
|
||||
}
|
||||
>
|
||||
|
|
@ -2742,18 +2738,16 @@ export function ReportDesignerRightPanel() {
|
|||
</div>
|
||||
|
||||
{/* 텍스트 워터마크 설정 */}
|
||||
{currentPage.watermark?.type === "text" && (
|
||||
{layoutConfig.watermark?.type === "text" && (
|
||||
<>
|
||||
<div>
|
||||
<Label className="text-xs">텍스트</Label>
|
||||
<Input
|
||||
value={currentPage.watermark?.text ?? ""}
|
||||
value={layoutConfig.watermark?.text ?? ""}
|
||||
onChange={(e) =>
|
||||
updatePageSettings(currentPageId, {
|
||||
watermark: {
|
||||
...currentPage.watermark!,
|
||||
text: e.target.value,
|
||||
},
|
||||
updateWatermark({
|
||||
...layoutConfig.watermark!,
|
||||
text: e.target.value,
|
||||
})
|
||||
}
|
||||
placeholder="DRAFT, 대외비 등"
|
||||
|
|
@ -2765,13 +2759,11 @@ export function ReportDesignerRightPanel() {
|
|||
<Label className="text-xs">폰트 크기</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={currentPage.watermark?.fontSize ?? 48}
|
||||
value={layoutConfig.watermark?.fontSize ?? 48}
|
||||
onChange={(e) =>
|
||||
updatePageSettings(currentPageId, {
|
||||
watermark: {
|
||||
...currentPage.watermark!,
|
||||
fontSize: Number(e.target.value),
|
||||
},
|
||||
updateWatermark({
|
||||
...layoutConfig.watermark!,
|
||||
fontSize: Number(e.target.value),
|
||||
})
|
||||
}
|
||||
className="mt-1"
|
||||
|
|
@ -2784,26 +2776,22 @@ export function ReportDesignerRightPanel() {
|
|||
<div className="mt-1 flex gap-1">
|
||||
<Input
|
||||
type="color"
|
||||
value={currentPage.watermark?.fontColor ?? "#cccccc"}
|
||||
value={layoutConfig.watermark?.fontColor ?? "#cccccc"}
|
||||
onChange={(e) =>
|
||||
updatePageSettings(currentPageId, {
|
||||
watermark: {
|
||||
...currentPage.watermark!,
|
||||
fontColor: e.target.value,
|
||||
},
|
||||
updateWatermark({
|
||||
...layoutConfig.watermark!,
|
||||
fontColor: e.target.value,
|
||||
})
|
||||
}
|
||||
className="h-9 w-12 cursor-pointer p-1"
|
||||
/>
|
||||
<Input
|
||||
type="text"
|
||||
value={currentPage.watermark?.fontColor ?? "#cccccc"}
|
||||
value={layoutConfig.watermark?.fontColor ?? "#cccccc"}
|
||||
onChange={(e) =>
|
||||
updatePageSettings(currentPageId, {
|
||||
watermark: {
|
||||
...currentPage.watermark!,
|
||||
fontColor: e.target.value,
|
||||
},
|
||||
updateWatermark({
|
||||
...layoutConfig.watermark!,
|
||||
fontColor: e.target.value,
|
||||
})
|
||||
}
|
||||
className="flex-1"
|
||||
|
|
@ -2815,7 +2803,7 @@ export function ReportDesignerRightPanel() {
|
|||
)}
|
||||
|
||||
{/* 이미지 워터마크 설정 */}
|
||||
{currentPage.watermark?.type === "image" && (
|
||||
{layoutConfig.watermark?.type === "image" && (
|
||||
<div>
|
||||
<Label className="text-xs">워터마크 이미지</Label>
|
||||
<div className="mt-1 flex gap-2">
|
||||
|
|
@ -2843,21 +2831,19 @@ export function ReportDesignerRightPanel() {
|
|||
) : (
|
||||
<>
|
||||
<Upload className="mr-2 h-4 w-4" />
|
||||
{currentPage.watermark?.imageUrl ? "이미지 변경" : "이미지 선택"}
|
||||
{layoutConfig.watermark?.imageUrl ? "이미지 변경" : "이미지 선택"}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
{currentPage.watermark?.imageUrl && (
|
||||
{layoutConfig.watermark?.imageUrl && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
updatePageSettings(currentPageId, {
|
||||
watermark: {
|
||||
...currentPage.watermark!,
|
||||
imageUrl: "",
|
||||
},
|
||||
updateWatermark({
|
||||
...layoutConfig.watermark!,
|
||||
imageUrl: "",
|
||||
})
|
||||
}
|
||||
>
|
||||
|
|
@ -2868,9 +2854,9 @@ export function ReportDesignerRightPanel() {
|
|||
<p className="text-muted-foreground mt-1 text-[10px]">
|
||||
JPG, PNG, GIF, WEBP (최대 5MB)
|
||||
</p>
|
||||
{currentPage.watermark?.imageUrl && (
|
||||
{layoutConfig.watermark?.imageUrl && (
|
||||
<p className="mt-1 max-w-full overflow-hidden text-ellipsis whitespace-nowrap text-xs text-indigo-600">
|
||||
현재: ...{currentPage.watermark.imageUrl.slice(-30)}
|
||||
현재: ...{layoutConfig.watermark.imageUrl.slice(-30)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -2880,13 +2866,11 @@ export function ReportDesignerRightPanel() {
|
|||
<div>
|
||||
<Label className="text-xs">배치 스타일</Label>
|
||||
<Select
|
||||
value={currentPage.watermark?.style ?? "diagonal"}
|
||||
value={layoutConfig.watermark?.style ?? "diagonal"}
|
||||
onValueChange={(value: "diagonal" | "center" | "tile") =>
|
||||
updatePageSettings(currentPageId, {
|
||||
watermark: {
|
||||
...currentPage.watermark!,
|
||||
style: value,
|
||||
},
|
||||
updateWatermark({
|
||||
...layoutConfig.watermark!,
|
||||
style: value,
|
||||
})
|
||||
}
|
||||
>
|
||||
|
|
@ -2902,19 +2886,17 @@ export function ReportDesignerRightPanel() {
|
|||
</div>
|
||||
|
||||
{/* 대각선/타일 회전 각도 */}
|
||||
{(currentPage.watermark?.style === "diagonal" ||
|
||||
currentPage.watermark?.style === "tile") && (
|
||||
{(layoutConfig.watermark?.style === "diagonal" ||
|
||||
layoutConfig.watermark?.style === "tile") && (
|
||||
<div>
|
||||
<Label className="text-xs">회전 각도</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={currentPage.watermark?.rotation ?? -45}
|
||||
value={layoutConfig.watermark?.rotation ?? -45}
|
||||
onChange={(e) =>
|
||||
updatePageSettings(currentPageId, {
|
||||
watermark: {
|
||||
...currentPage.watermark!,
|
||||
rotation: Number(e.target.value),
|
||||
},
|
||||
updateWatermark({
|
||||
...layoutConfig.watermark!,
|
||||
rotation: Number(e.target.value),
|
||||
})
|
||||
}
|
||||
className="mt-1"
|
||||
|
|
@ -2929,17 +2911,15 @@ export function ReportDesignerRightPanel() {
|
|||
<div className="flex items-center justify-between">
|
||||
<Label className="text-xs">투명도</Label>
|
||||
<span className="text-muted-foreground text-xs">
|
||||
{Math.round((currentPage.watermark?.opacity ?? 0.3) * 100)}%
|
||||
{Math.round((layoutConfig.watermark?.opacity ?? 0.3) * 100)}%
|
||||
</span>
|
||||
</div>
|
||||
<Slider
|
||||
value={[(currentPage.watermark?.opacity ?? 0.3) * 100]}
|
||||
value={[(layoutConfig.watermark?.opacity ?? 0.3) * 100]}
|
||||
onValueChange={(value) =>
|
||||
updatePageSettings(currentPageId, {
|
||||
watermark: {
|
||||
...currentPage.watermark!,
|
||||
opacity: value[0] / 100,
|
||||
},
|
||||
updateWatermark({
|
||||
...layoutConfig.watermark!,
|
||||
opacity: value[0] / 100,
|
||||
})
|
||||
}
|
||||
min={5}
|
||||
|
|
@ -2955,17 +2935,15 @@ export function ReportDesignerRightPanel() {
|
|||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() =>
|
||||
updatePageSettings(currentPageId, {
|
||||
watermark: {
|
||||
...currentPage.watermark!,
|
||||
type: "text",
|
||||
text: "DRAFT",
|
||||
fontSize: 64,
|
||||
fontColor: "#cccccc",
|
||||
style: "diagonal",
|
||||
opacity: 0.2,
|
||||
rotation: -45,
|
||||
},
|
||||
updateWatermark({
|
||||
...layoutConfig.watermark!,
|
||||
type: "text",
|
||||
text: "DRAFT",
|
||||
fontSize: 64,
|
||||
fontColor: "#cccccc",
|
||||
style: "diagonal",
|
||||
opacity: 0.2,
|
||||
rotation: -45,
|
||||
})
|
||||
}
|
||||
>
|
||||
|
|
@ -2975,17 +2953,15 @@ export function ReportDesignerRightPanel() {
|
|||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() =>
|
||||
updatePageSettings(currentPageId, {
|
||||
watermark: {
|
||||
...currentPage.watermark!,
|
||||
type: "text",
|
||||
text: "대외비",
|
||||
fontSize: 64,
|
||||
fontColor: "#ff0000",
|
||||
style: "diagonal",
|
||||
opacity: 0.15,
|
||||
rotation: -45,
|
||||
},
|
||||
updateWatermark({
|
||||
...layoutConfig.watermark!,
|
||||
type: "text",
|
||||
text: "대외비",
|
||||
fontSize: 64,
|
||||
fontColor: "#ff0000",
|
||||
style: "diagonal",
|
||||
opacity: 0.15,
|
||||
rotation: -45,
|
||||
})
|
||||
}
|
||||
>
|
||||
|
|
@ -2995,17 +2971,15 @@ export function ReportDesignerRightPanel() {
|
|||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() =>
|
||||
updatePageSettings(currentPageId, {
|
||||
watermark: {
|
||||
...currentPage.watermark!,
|
||||
type: "text",
|
||||
text: "SAMPLE",
|
||||
fontSize: 48,
|
||||
fontColor: "#888888",
|
||||
style: "tile",
|
||||
opacity: 0.1,
|
||||
rotation: -30,
|
||||
},
|
||||
updateWatermark({
|
||||
...layoutConfig.watermark!,
|
||||
type: "text",
|
||||
text: "SAMPLE",
|
||||
fontSize: 48,
|
||||
fontColor: "#888888",
|
||||
style: "tile",
|
||||
opacity: 0.1,
|
||||
rotation: -30,
|
||||
})
|
||||
}
|
||||
>
|
||||
|
|
@ -3015,16 +2989,14 @@ export function ReportDesignerRightPanel() {
|
|||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() =>
|
||||
updatePageSettings(currentPageId, {
|
||||
watermark: {
|
||||
...currentPage.watermark!,
|
||||
type: "text",
|
||||
text: "COPY",
|
||||
fontSize: 56,
|
||||
fontColor: "#aaaaaa",
|
||||
style: "center",
|
||||
opacity: 0.25,
|
||||
},
|
||||
updateWatermark({
|
||||
...layoutConfig.watermark!,
|
||||
type: "text",
|
||||
text: "COPY",
|
||||
fontSize: 56,
|
||||
fontColor: "#aaaaaa",
|
||||
style: "center",
|
||||
opacity: 0.25,
|
||||
})
|
||||
}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -924,7 +924,7 @@ export function ReportPreviewModal({ isOpen, onClose }: ReportPreviewModalProps)
|
|||
page.background_color,
|
||||
pageIndex,
|
||||
totalPages,
|
||||
page.watermark,
|
||||
layoutConfig.watermark, // 전체 페이지 공유 워터마크
|
||||
),
|
||||
)
|
||||
.join('<div style="page-break-after: always;"></div>');
|
||||
|
|
@ -1156,10 +1156,10 @@ export function ReportPreviewModal({ isOpen, onClose }: ReportPreviewModalProps)
|
|||
backgroundColor: page.background_color,
|
||||
}}
|
||||
>
|
||||
{/* 워터마크 렌더링 */}
|
||||
{page.watermark?.enabled && (
|
||||
{/* 워터마크 렌더링 (전체 페이지 공유) */}
|
||||
{layoutConfig.watermark?.enabled && (
|
||||
<PreviewWatermarkLayer
|
||||
watermark={page.watermark}
|
||||
watermark={layoutConfig.watermark}
|
||||
pageWidth={page.width}
|
||||
pageHeight={page.height}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { createContext, useContext, useState, useCallback, ReactNode, useEffect, useRef } from "react";
|
||||
import { ComponentConfig, ReportDetail, ReportLayout, ReportPage, ReportLayoutConfig } from "@/types/report";
|
||||
import { ComponentConfig, ReportDetail, ReportLayout, ReportPage, ReportLayoutConfig, WatermarkConfig } from "@/types/report";
|
||||
import { reportApi } from "@/lib/api/reportApi";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
|
@ -40,6 +40,7 @@ interface ReportDesignerContextType {
|
|||
reorderPages: (sourceIndex: number, targetIndex: number) => void;
|
||||
selectPage: (pageId: string) => void;
|
||||
updatePageSettings: (pageId: string, settings: Partial<ReportPage>) => void;
|
||||
updateWatermark: (watermark: WatermarkConfig | undefined) => void; // 전체 페이지 공유 워터마크
|
||||
|
||||
// 컴포넌트 (현재 페이지)
|
||||
components: ComponentConfig[]; // currentPage의 components (읽기 전용)
|
||||
|
|
@ -988,10 +989,19 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin
|
|||
|
||||
const updatePageSettings = useCallback((pageId: string, settings: Partial<ReportPage>) => {
|
||||
setLayoutConfig((prev) => ({
|
||||
...prev,
|
||||
pages: prev.pages.map((page) => (page.page_id === pageId ? { ...page, ...settings } : page)),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
// 전체 페이지 공유 워터마크 업데이트
|
||||
const updateWatermark = useCallback((watermark: WatermarkConfig | undefined) => {
|
||||
setLayoutConfig((prev) => ({
|
||||
...prev,
|
||||
watermark,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
// 리포트 및 레이아웃 로드
|
||||
const loadLayout = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
|
|
@ -1471,6 +1481,7 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin
|
|||
reorderPages,
|
||||
selectPage,
|
||||
updatePageSettings,
|
||||
updateWatermark,
|
||||
|
||||
// 컴포넌트 (현재 페이지)
|
||||
components,
|
||||
|
|
|
|||
|
|
@ -113,12 +113,12 @@ export interface ReportPage {
|
|||
};
|
||||
background_color: string;
|
||||
components: ComponentConfig[];
|
||||
watermark?: WatermarkConfig;
|
||||
}
|
||||
|
||||
// 레이아웃 설정 (페이지 기반)
|
||||
export interface ReportLayoutConfig {
|
||||
pages: ReportPage[];
|
||||
watermark?: WatermarkConfig; // 전체 페이지 공유 워터마크
|
||||
}
|
||||
|
||||
// 컴포넌트 설정
|
||||
|
|
|
|||
Loading…
Reference in New Issue