ERP-node/frontend/lib/registry/pop-components/pop-dashboard/items/KpiCard.tsx

110 lines
2.9 KiB
TypeScript
Raw Normal View History

"use client";
/**
* KPI
*
* + +
* CSS Container Query로
*/
import React from "react";
import type { DashboardItem } from "../../types";
import { abbreviateNumber } from "../utils/formula";
// ===== Props =====
export interface KpiCardProps {
item: DashboardItem;
data: number | null;
/** 이전 기간 대비 증감 퍼센트 (선택) */
trendValue?: number | null;
/** 수식 결과 표시 문자열 (formula가 있을 때) */
formulaDisplay?: string | null;
}
// ===== 증감 표시 =====
function TrendIndicator({ value }: { value: number }) {
const isPositive = value > 0;
const isZero = value === 0;
const color = isPositive
? "text-emerald-600"
: isZero
? "text-muted-foreground"
: "text-rose-600";
const arrow = isPositive ? "↑" : isZero ? "→" : "↓";
return (
<span className={`inline-flex items-center gap-0.5 text-xs font-medium ${color}`}>
<span>{arrow}</span>
<span>{Math.abs(value).toFixed(1)}%</span>
</span>
);
}
// ===== 색상 구간 판정 =====
function getColorForValue(
value: number,
ranges?: { min: number; max: number; color: string }[]
): string | undefined {
if (!ranges?.length) return undefined;
const match = ranges.find((r) => value >= r.min && value <= r.max);
return match?.color;
}
// ===== 메인 컴포넌트 =====
export function KpiCardComponent({
item,
data,
trendValue,
formulaDisplay,
}: KpiCardProps) {
const { visibility, kpiConfig } = item;
const displayValue = data ?? 0;
const valueColor = getColorForValue(displayValue, kpiConfig?.colorRanges);
return (
<div className="@container flex h-full w-full flex-col justify-center p-3">
{/* 라벨 */}
{visibility.showLabel && (
<p className="text-xs text-muted-foreground @[250px]:text-sm">
{item.label}
</p>
)}
{/* 메인 값 */}
{visibility.showValue && (
<div className="flex items-baseline gap-1">
<span
className="text-xl font-bold @[200px]:text-3xl @[350px]:text-4xl @[500px]:text-5xl"
style={valueColor ? { color: valueColor } : undefined}
>
{formulaDisplay ?? abbreviateNumber(displayValue)}
</span>
{/* 단위 */}
{visibility.showUnit && kpiConfig?.unit && (
<span className="text-xs text-muted-foreground @[200px]:text-sm">
{kpiConfig.unit}
</span>
)}
</div>
)}
{/* 증감율 */}
{visibility.showTrend && trendValue != null && (
<TrendIndicator value={trendValue} />
)}
{/* 보조 라벨 (수식 표시 등) */}
{visibility.showSubLabel && formulaDisplay && (
<p className="text-xs text-muted-foreground @[350px]:text-sm">
{item.formula?.values.map((v) => v.label).join(" / ")}
</p>
)}
</div>
);
}