109 lines
4.0 KiB
TypeScript
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>
|
|
);
|
|
}
|