ERP-node/frontend/components/report/designer/properties/CalculationProperties.tsx

345 lines
14 KiB
TypeScript

"use client";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Calculator } from "lucide-react";
import { useReportDesigner } from "@/contexts/ReportDesignerContext";
import type { ComponentConfig } from "@/types/report";
interface Props {
component: ComponentConfig;
/** 우측 패널: "style" | 모달: "data" | 미전달: 전체 표시 (하위 호환) */
section?: "style" | "data";
}
export function CalculationProperties({ component, section }: Props) {
const { updateComponent, queries, getQueryResult } = useReportDesigner();
const showStyle = !section || section === "style";
const showData = !section || section === "data";
return (
<>
{/* 표시 설정 — 우측 패널(section="style")에서 표시 */}
{showStyle && (
<div className="mt-4 space-y-3 rounded-xl border border-orange-200 bg-orange-50/50 p-4">
<div className="flex items-center gap-2 text-sm font-semibold text-orange-700">
<Calculator className="h-4 w-4" />
</div>
{/* 라벨 너비 */}
<div>
<Label className="text-xs"> (px)</Label>
<Input
type="number"
value={component.labelWidth || 120}
onChange={(e) => updateComponent(component.id, { labelWidth: Number(e.target.value) })}
min={60}
max={200}
className="h-9"
/>
</div>
{/* 숫자 포맷 */}
<div>
<Label className="text-xs"> </Label>
<Select
value={component.numberFormat || "currency"}
onValueChange={(value) =>
updateComponent(component.id, { numberFormat: value as "none" | "comma" | "currency" })
}
>
<SelectTrigger className="h-9">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="none"></SelectItem>
<SelectItem value="comma"> </SelectItem>
<SelectItem value="currency"> ()</SelectItem>
</SelectContent>
</Select>
</div>
{/* 통화 접미사 */}
{component.numberFormat === "currency" && (
<div>
<Label className="text-xs"> </Label>
<Input
type="text"
value={component.currencySuffix || "원"}
onChange={(e) => updateComponent(component.id, { currencySuffix: e.target.value })}
placeholder="원"
className="h-9"
/>
</div>
)}
{/* 폰트 크기 설정 */}
<div className="grid grid-cols-3 gap-2">
<div>
<Label className="text-xs"> </Label>
<Input
type="number"
value={component.labelFontSize || 13}
onChange={(e) => updateComponent(component.id, { labelFontSize: Number(e.target.value) })}
min={10}
max={20}
className="h-9"
/>
</div>
<div>
<Label className="text-xs"> </Label>
<Input
type="number"
value={component.valueFontSize || 13}
onChange={(e) => updateComponent(component.id, { valueFontSize: Number(e.target.value) })}
min={10}
max={20}
className="h-9"
/>
</div>
<div>
<Label className="text-xs"> </Label>
<Input
type="number"
value={component.resultFontSize || 16}
onChange={(e) => updateComponent(component.id, { resultFontSize: Number(e.target.value) })}
min={12}
max={24}
className="h-9"
/>
</div>
</div>
{/* 색상 설정 */}
<div className="grid grid-cols-3 gap-2">
<div>
<Label className="text-xs"> </Label>
<Input
type="color"
value={component.labelColor || "#374151"}
onChange={(e) => updateComponent(component.id, { labelColor: e.target.value })}
className="h-9 w-full cursor-pointer p-1"
/>
</div>
<div>
<Label className="text-xs"> </Label>
<Input
type="color"
value={component.valueColor || "#000000"}
onChange={(e) => updateComponent(component.id, { valueColor: e.target.value })}
className="h-9 w-full cursor-pointer p-1"
/>
</div>
<div>
<Label className="text-xs"> </Label>
<Input
type="color"
value={component.resultColor || "#2563eb"}
onChange={(e) => updateComponent(component.id, { resultColor: e.target.value })}
className="h-9 w-full cursor-pointer p-1"
/>
</div>
</div>
</div>
)}
{/* 계산 항목 — 모달(section="data")에서 표시 */}
{showData && (
<div className="mt-4 space-y-3 rounded-xl border border-orange-200 bg-orange-50/50 p-4">
<div className="flex items-center gap-2 text-sm font-semibold text-orange-700">
<Calculator className="h-4 w-4" />
</div>
{/* 결과 라벨 */}
<div>
<Label className="text-xs"> </Label>
<Input
type="text"
value={component.resultLabel || "합계"}
onChange={(e) => updateComponent(component.id, { resultLabel: e.target.value })}
placeholder="합계 금액"
className="h-9"
/>
</div>
{/* 쿼리 선택 (데이터 바인딩용) */}
<div>
<Label className="text-xs"> ()</Label>
<Select
value={component.queryId || "none"}
onValueChange={(value) =>
updateComponent(component.id, { queryId: value === "none" ? undefined : value })
}
>
<SelectTrigger className="h-9">
<SelectValue placeholder="쿼리 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none"> </SelectItem>
{queries.map((q) => (
<SelectItem key={q.id} value={q.id}>
{q.name} ({q.type})
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* 계산 항목 목록 관리 */}
<div className="border-t pt-3">
<div className="mb-2 flex items-center justify-between">
<Label className="text-xs font-semibold"> </Label>
<Button
size="sm"
variant="outline"
className="h-6 text-xs"
onClick={() => {
const currentItems = component.calcItems || [];
updateComponent(component.id, {
calcItems: [
...currentItems,
{
label: `항목${currentItems.length + 1}`,
value: 0,
operator: "+" as const,
fieldName: "",
},
],
});
}}
>
+
</Button>
</div>
{/* 항목 리스트 — 개별 항목 카드(rounded border bg-white p-2)는 유지 */}
<div className="max-h-48 space-y-2 overflow-y-auto">
{(component.calcItems || []).map((item, index: number) => (
<div key={index} className="rounded border bg-white p-2">
<div className="mb-1 flex items-center justify-between">
<span className="text-xs font-medium"> {index + 1}</span>
<Button
size="sm"
variant="ghost"
className="h-5 w-5 p-0 text-red-500 hover:text-red-700"
onClick={() => {
const currentItems = [...(component.calcItems || [])];
currentItems.splice(index, 1);
updateComponent(component.id, { calcItems: currentItems });
}}
>
x
</Button>
</div>
<div className={`grid gap-1 ${index === 0 ? "grid-cols-1" : "grid-cols-3"}`}>
<div className={index === 0 ? "" : "col-span-2"}>
<Label className="text-[10px]"></Label>
<Input
type="text"
value={item.label}
onChange={(e) => {
const currentItems = [...(component.calcItems || [])];
currentItems[index] = { ...currentItems[index], label: e.target.value };
updateComponent(component.id, { calcItems: currentItems });
}}
className="h-6 text-xs"
placeholder="항목명"
/>
</div>
{index > 0 && (
<div>
<Label className="text-[10px]"></Label>
<Select
value={item.operator}
onValueChange={(value) => {
const currentItems = [...(component.calcItems || [])];
currentItems[index] = {
...currentItems[index],
operator: value as "+" | "-" | "x" | "÷",
};
updateComponent(component.id, { calcItems: currentItems });
}}
>
<SelectTrigger className="h-6 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="+">+</SelectItem>
<SelectItem value="-">-</SelectItem>
<SelectItem value="x">x</SelectItem>
<SelectItem value="÷">÷</SelectItem>
</SelectContent>
</Select>
</div>
)}
</div>
<div className="mt-1">
{component.queryId ? (
<div>
<Label className="text-[10px]"></Label>
<Select
value={item.fieldName || "none"}
onValueChange={(value) => {
const currentItems = [...(component.calcItems || [])];
currentItems[index] = {
...currentItems[index],
fieldName: value === "none" ? "" : value,
};
updateComponent(component.id, { calcItems: currentItems });
}}
>
<SelectTrigger className="h-6 text-xs">
<SelectValue placeholder="필드 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none"> </SelectItem>
{(() => {
const query = queries.find((q) => q.id === component.queryId);
const result = query ? getQueryResult(query.id) : null;
if (result && result.fields) {
return result.fields.map((field: string) => (
<SelectItem key={field} value={field}>
{field}
</SelectItem>
));
}
return null;
})()}
</SelectContent>
</Select>
</div>
) : (
<div>
<Label className="text-[10px]"></Label>
<Input
type="number"
value={item.value}
onChange={(e) => {
const currentItems = [...(component.calcItems || [])];
currentItems[index] = {
...currentItems[index],
value: Number(e.target.value),
};
updateComponent(component.id, { calcItems: currentItems });
}}
className="h-6 text-xs"
placeholder="0"
/>
</div>
)}
</div>
</div>
))}
</div>
</div>
</div>
)}
</>
);
}