207 lines
7.6 KiB
TypeScript
207 lines
7.6 KiB
TypeScript
"use client";
|
|
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { ComponentConfig } from "@/types/report";
|
|
import { useReportDesigner } from "@/contexts/ReportDesignerContext";
|
|
|
|
const FONT_FAMILIES = [
|
|
"Malgun Gothic",
|
|
"NanumGothic",
|
|
"NanumMyeongjo",
|
|
"굴림",
|
|
"돋움",
|
|
"바탕",
|
|
"Times New Roman",
|
|
"Arial",
|
|
];
|
|
|
|
interface Props {
|
|
componentIds: string[];
|
|
components: ComponentConfig[];
|
|
}
|
|
|
|
type EditableField =
|
|
| "fontSize"
|
|
| "fontFamily"
|
|
| "fontColor"
|
|
| "fontWeight"
|
|
| "textAlign"
|
|
| "backgroundColor"
|
|
| "borderWidth"
|
|
| "borderColor";
|
|
|
|
function getCommonValue<T>(components: ComponentConfig[], field: EditableField): T | "mixed" {
|
|
const values = components.map((c) => c[field as keyof ComponentConfig]);
|
|
const first = values[0];
|
|
return values.every((v) => v === first) ? (first as T) : "mixed";
|
|
}
|
|
|
|
export function MultiSelectProperties({ componentIds, components }: Props) {
|
|
const { updateComponent } = useReportDesigner();
|
|
|
|
const selected = components.filter((c) => componentIds.includes(c.id));
|
|
const count = selected.length;
|
|
|
|
const applyToAll = (patch: Partial<ComponentConfig>) => {
|
|
componentIds.forEach((id) => updateComponent(id, patch));
|
|
};
|
|
|
|
const commonFontSize = getCommonValue<number>(selected, "fontSize");
|
|
const commonFontFamily = getCommonValue<string>(selected, "fontFamily");
|
|
const commonFontColor = getCommonValue<string>(selected, "fontColor");
|
|
const commonFontWeight = getCommonValue<string>(selected, "fontWeight");
|
|
const commonTextAlign = getCommonValue<string>(selected, "textAlign");
|
|
const commonBgColor = getCommonValue<string>(selected, "backgroundColor");
|
|
const commonBorderWidth = getCommonValue<number>(selected, "borderWidth");
|
|
const commonBorderColor = getCommonValue<string>(selected, "borderColor");
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader className="pb-3">
|
|
<CardTitle className="text-sm">{count}개 컴포넌트 선택됨</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3">
|
|
<p className="text-xs text-gray-500">공통 속성을 변경하면 선택된 모든 컴포넌트에 적용됩니다.</p>
|
|
|
|
{/* 글꼴 크기 */}
|
|
<div>
|
|
<Label className="text-xs">글꼴 크기</Label>
|
|
<Input
|
|
type="number"
|
|
value={commonFontSize === "mixed" ? "" : (commonFontSize as number) || 13}
|
|
placeholder={commonFontSize === "mixed" ? "혼합" : undefined}
|
|
onChange={(e) => {
|
|
const val = parseInt(e.target.value);
|
|
if (val > 0) applyToAll({ fontSize: val });
|
|
}}
|
|
className="h-8"
|
|
/>
|
|
</div>
|
|
|
|
{/* 폰트 패밀리 */}
|
|
<div>
|
|
<Label className="text-xs">폰트 패밀리</Label>
|
|
<Select
|
|
value={commonFontFamily === "mixed" ? "" : (commonFontFamily as string) || "Malgun Gothic"}
|
|
onValueChange={(value) => applyToAll({ fontFamily: value })}
|
|
>
|
|
<SelectTrigger className="h-8">
|
|
<SelectValue placeholder={commonFontFamily === "mixed" ? "혼합" : "선택"} />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{FONT_FAMILIES.map((f) => (
|
|
<SelectItem key={f} value={f} style={{ fontFamily: f }}>
|
|
{f}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{/* 글꼴 색상 */}
|
|
<div>
|
|
<Label className="text-xs">글꼴 색상</Label>
|
|
<div className="flex gap-2">
|
|
<Input
|
|
type="color"
|
|
value={commonFontColor === "mixed" ? "#000000" : (commonFontColor as string) || "#000000"}
|
|
onChange={(e) => applyToAll({ fontColor: e.target.value })}
|
|
className="h-8 w-16"
|
|
/>
|
|
<Input
|
|
type="text"
|
|
value={commonFontColor === "mixed" ? "" : (commonFontColor as string) || "#000000"}
|
|
placeholder={commonFontColor === "mixed" ? "혼합" : undefined}
|
|
onChange={(e) => applyToAll({ fontColor: e.target.value })}
|
|
className="h-8 flex-1 font-mono text-xs"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 글꼴 굵기 */}
|
|
<div>
|
|
<Label className="text-xs">글꼴 굵기</Label>
|
|
<Select
|
|
value={commonFontWeight === "mixed" ? "" : (commonFontWeight as string) || "normal"}
|
|
onValueChange={(value) => applyToAll({ fontWeight: value as "normal" | "bold" })}
|
|
>
|
|
<SelectTrigger className="h-8">
|
|
<SelectValue placeholder={commonFontWeight === "mixed" ? "혼합" : "선택"} />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="normal">보통</SelectItem>
|
|
<SelectItem value="bold">굵게</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{/* 텍스트 정렬 */}
|
|
<div>
|
|
<Label className="text-xs">텍스트 정렬</Label>
|
|
<Select
|
|
value={commonTextAlign === "mixed" ? "" : (commonTextAlign as string) || "left"}
|
|
onValueChange={(value) => applyToAll({ textAlign: value as "left" | "center" | "right" })}
|
|
>
|
|
<SelectTrigger className="h-8">
|
|
<SelectValue placeholder={commonTextAlign === "mixed" ? "혼합" : "선택"} />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="left">왼쪽</SelectItem>
|
|
<SelectItem value="center">가운데</SelectItem>
|
|
<SelectItem value="right">오른쪽</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{/* 배경 색상 */}
|
|
<div>
|
|
<Label className="text-xs">배경 색상</Label>
|
|
<div className="flex gap-2">
|
|
<Input
|
|
type="color"
|
|
value={commonBgColor === "mixed" ? "#ffffff" : (commonBgColor as string) || "#ffffff"}
|
|
onChange={(e) => applyToAll({ backgroundColor: e.target.value })}
|
|
className="h-8 w-16"
|
|
/>
|
|
<Input
|
|
type="text"
|
|
value={commonBgColor === "mixed" ? "" : (commonBgColor as string) || "#ffffff"}
|
|
placeholder={commonBgColor === "mixed" ? "혼합" : undefined}
|
|
onChange={(e) => applyToAll({ backgroundColor: e.target.value })}
|
|
className="h-8 flex-1 font-mono text-xs"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 테두리 */}
|
|
<div className="grid grid-cols-2 gap-2">
|
|
<div>
|
|
<Label className="text-xs">테두리 두께</Label>
|
|
<Input
|
|
type="number"
|
|
min="0"
|
|
max="10"
|
|
value={commonBorderWidth === "mixed" ? "" : (commonBorderWidth as number) || 0}
|
|
placeholder={commonBorderWidth === "mixed" ? "혼합" : undefined}
|
|
onChange={(e) => applyToAll({ borderWidth: parseInt(e.target.value) || 0 })}
|
|
className="h-8"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label className="text-xs">테두리 색상</Label>
|
|
<Input
|
|
type="color"
|
|
value={commonBorderColor === "mixed" ? "#cccccc" : (commonBorderColor as string) || "#cccccc"}
|
|
onChange={(e) => applyToAll({ borderColor: e.target.value })}
|
|
className="h-8"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|