ERP-node/frontend/components/report/designer/renderers/CalculationRenderer.tsx

109 lines
4.0 KiB
TypeScript

"use client";
import type { CalculationRendererProps } from "./types";
export function CalculationRenderer({ component, getQueryResult }: CalculationRendererProps) {
const calcItems = component.calcItems || [];
const resultLabel = component.resultLabel || "합계";
const calcLabelWidth = component.labelWidth || 120;
const calcLabelFontSize = component.labelFontSize || 13;
const calcValueFontSize = component.valueFontSize || 13;
const calcResultFontSize = component.resultFontSize || 16;
const calcLabelColor = component.labelColor || "#374151";
const calcValueColor = component.valueColor || "#000000";
const calcResultColor = component.resultColor || "#2563eb";
const numberFormat = component.numberFormat || "currency";
const currencySuffix = component.currencySuffix || "원";
const formatNumber = (num: number): string => {
if (numberFormat === "none") return String(num);
if (numberFormat === "comma") return num.toLocaleString();
if (numberFormat === "currency") return num.toLocaleString() + currencySuffix;
return String(num);
};
const getCalcItemValue = (item: {
label: string;
value: number | string;
operator: string;
fieldName?: string;
}): number => {
if (item.fieldName && component.queryId) {
const queryResult = getQueryResult(component.queryId);
if (queryResult && queryResult.rows && queryResult.rows.length > 0) {
const row = queryResult.rows[0];
const val = row[item.fieldName];
return typeof val === "number" ? val : parseFloat(String(val)) || 0;
}
}
return typeof item.value === "number" ? item.value : parseFloat(String(item.value)) || 0;
};
const calculateResult = (): number => {
if (calcItems.length === 0) return 0;
let result = getCalcItemValue(
calcItems[0] as { label: string; value: number | string; operator: string; fieldName?: string },
);
for (let i = 1; i < calcItems.length; i++) {
const item = calcItems[i];
const val = getCalcItemValue(
item as { label: string; value: number | string; operator: string; fieldName?: string },
);
switch (item.operator) {
case "+":
result += val;
break;
case "-":
result -= val;
break;
case "x":
result *= val;
break;
case "÷":
result = val !== 0 ? result / val : result;
break;
}
}
return result;
};
const calcResult = calculateResult();
return (
<div className="flex h-full w-full flex-col overflow-hidden">
<div className="flex-1 overflow-auto px-2 py-1">
{calcItems.map(
(item: { label: string; value: number | string; operator: string; fieldName?: string }, index: number) => {
const itemValue = getCalcItemValue(item);
return (
<div key={index} className="flex items-center justify-between py-1">
<span
className="flex-shrink-0"
style={{ width: `${calcLabelWidth}px`, fontSize: `${calcLabelFontSize}px`, color: calcLabelColor }}
>
{item.label}
</span>
<span className="text-right" style={{ fontSize: `${calcValueFontSize}px`, color: calcValueColor }}>
{formatNumber(itemValue)}
</span>
</div>
);
},
)}
</div>
<div className="mx-1 flex-shrink-0 border-t" style={{ borderColor: component.borderColor || "#374151" }} />
<div className="flex items-center justify-between px-2 py-2">
<span
className="font-semibold"
style={{ width: `${calcLabelWidth}px`, fontSize: `${calcResultFontSize}px`, color: calcLabelColor }}
>
{resultLabel}
</span>
<span className="text-right font-bold" style={{ fontSize: `${calcResultFontSize}px`, color: calcResultColor }}>
{formatNumber(calcResult)}
</span>
</div>
</div>
);
}