"use client"; import type { CardRendererProps, QueryResult } from "./types"; import type { CardLayoutConfig, CardLayoutRow, CardElement, CardHeaderElement, CardDataCellElement, CardDividerElement, CardBadgeElement, CardImageElement, CardNumberElement, CardDateElement, CardLinkElement, CardStatusElement, CardSpacerElement, CardStaticTextElement, } from "@/types/report"; import * as LucideIcons from "lucide-react"; // ───────────────────────────────────────────────────────────────────────────── // 기존 cardItems 방식 렌더러 (하위 호환) // ───────────────────────────────────────────────────────────────────────────── function CardListRenderer({ component, getQueryResult }: CardRendererProps) { const cardTitle = component.cardTitle || "정보 카드"; const cardItems = component.cardItems || []; const labelWidth = component.labelWidth || 80; const showCardTitle = component.showCardTitle !== false; const titleFontSize = component.titleFontSize || 14; const labelFontSize = component.labelFontSize || 13; const valueFontSize = component.valueFontSize || 13; const titleColor = component.titleColor || "#1e40af"; const labelColor = component.labelColor || "#374151"; const valueColor = component.valueColor || "#000000"; const getCardItemValue = (item: { label: string; value: string; fieldName?: string; }) => { if (item.fieldName && component.queryId) { const queryResult = getQueryResult(component.queryId); if (queryResult && queryResult.rows && queryResult.rows.length > 0) { const row = queryResult.rows[0]; return row[item.fieldName] !== undefined ? String(row[item.fieldName]) : item.value; } } return item.value; }; return (
{showCardTitle && ( <>
{cardTitle}
)}
{cardItems.map( ( item: { label: string; value: string; fieldName?: string }, index: number, ) => (
{item.label} {getCardItemValue(item)}
), )}
); } // ───────────────────────────────────────────────────────────────────────────── // v3 요소별 렌더러 // ───────────────────────────────────────────────────────────────────────────── interface ElementRendererProps { element: CardElement; getQueryResult: (queryId: string) => QueryResult | null; queryId?: string; config: CardLayoutConfig; } function CardHeaderRenderer({ element, config, }: { element: CardHeaderElement; config: CardLayoutConfig; }) { const titleFontSize = element.titleFontSize || config.headerTitleFontSize || 14; const titleColor = element.titleColor || config.headerTitleColor || "#1e40af"; const iconColor = element.iconColor || titleColor; const IconComponent = element.icon ? (LucideIcons as Record>)[element.icon] : null; return (
{IconComponent && ( )} {element.title}
); } function CardDataCellRenderer({ element, getQueryResult, queryId, config, }: { element: CardDataCellElement; getQueryResult: (queryId: string) => QueryResult | null; queryId?: string; config: CardLayoutConfig; }) { const labelFontSize = element.labelFontSize || config.labelFontSize || 13; const labelColor = element.labelColor || config.labelColor || "#374151"; const valueFontSize = element.valueFontSize || config.valueFontSize || 13; const valueColor = element.valueColor || config.valueColor || "#000000"; const getValue = (): string => { if (element.columnName && queryId) { const queryResult = getQueryResult(queryId); if (queryResult && queryResult.rows && queryResult.rows.length > 0) { const row = queryResult.rows[0]; const value = row[element.columnName]; return value !== undefined && value !== null ? String(value) : ""; } } return ""; }; const value = getValue() || "-"; if (element.direction === "vertical") { return (
{element.label} {value}
); } return (
{element.label} {value}
); } function CardDividerRenderer({ element, config, }: { element: CardDividerElement; config: CardLayoutConfig; }) { const thickness = element.thickness || config.dividerThickness || 1; const color = element.color || config.dividerColor || "#e5e7eb"; const style = element.style || "solid"; return (
); } function CardBadgeRenderer({ element, getQueryResult, queryId, }: { element: CardBadgeElement; getQueryResult: (queryId: string) => QueryResult | null; queryId?: string; config: CardLayoutConfig; }) { const getValue = (): string => { if (element.columnName && queryId) { const queryResult = getQueryResult(queryId); if (queryResult && queryResult.rows && queryResult.rows.length > 0) { const row = queryResult.rows[0]; const value = row[element.columnName]; return value !== undefined && value !== null ? String(value) : ""; } } return ""; }; const value = getValue(); const bgColor = element.colorMap?.[value] || "#e5e7eb"; return (
{element.label && ( {element.label} )} {value || "-"}
); } function getFieldValue( columnName: string | undefined, queryId: string | undefined, getQueryResult: (id: string) => QueryResult | null, ): string { if (!columnName || !queryId) return ""; const result = getQueryResult(queryId); if (result?.rows?.length) { const val = result.rows[0][columnName]; return val !== undefined && val !== null ? String(val) : ""; } return ""; } function formatNumber(raw: string, format?: string, suffix?: string): string { const num = parseFloat(raw); if (isNaN(num)) return raw || "-"; if (format === "comma" || format === "currency") { const formatted = num.toLocaleString("ko-KR"); return format === "currency" ? `${formatted}${suffix || "원"}` : formatted; } return raw; } function CardImageRenderer({ element, getQueryResult, queryId, }: { element: CardImageElement; getQueryResult: (id: string) => QueryResult | null; queryId?: string; }) { const url = getFieldValue(element.columnName, queryId, getQueryResult); return (
{url ? ( {element.altText ) : (
이미지
)}
); } function CardNumberRenderer({ element, getQueryResult, queryId, config, }: { element: CardNumberElement; getQueryResult: (id: string) => QueryResult | null; queryId?: string; config: CardLayoutConfig; }) { const raw = getFieldValue(element.columnName, queryId, getQueryResult); const value = formatNumber(raw, element.numberFormat, element.currencySuffix); const labelFontSize = element.labelFontSize || config.labelFontSize || 13; const labelColor = element.labelColor || config.labelColor || "#374151"; const valueFontSize = element.valueFontSize || config.valueFontSize || 13; const valueColor = element.valueColor || config.valueColor || "#000000"; return (
{element.label} {value}
); } function CardDateRenderer({ element, getQueryResult, queryId, config, }: { element: CardDateElement; getQueryResult: (id: string) => QueryResult | null; queryId?: string; config: CardLayoutConfig; }) { const raw = getFieldValue(element.columnName, queryId, getQueryResult); const labelFontSize = element.labelFontSize || config.labelFontSize || 13; const labelColor = element.labelColor || config.labelColor || "#374151"; const valueFontSize = element.valueFontSize || config.valueFontSize || 13; const valueColor = element.valueColor || config.valueColor || "#000000"; let displayValue = raw || "-"; if (raw && element.dateFormat) { try { const d = new Date(raw); if (!isNaN(d.getTime())) { displayValue = element.dateFormat .replace("YYYY", String(d.getFullYear())) .replace("MM", String(d.getMonth() + 1).padStart(2, "0")) .replace("DD", String(d.getDate()).padStart(2, "0")); } } catch { displayValue = raw; } } return (
{element.label} {displayValue}
); } function CardLinkRenderer({ element, getQueryResult, queryId, }: { element: CardLinkElement; getQueryResult: (id: string) => QueryResult | null; queryId?: string; }) { const url = getFieldValue(element.columnName, queryId, getQueryResult); const text = element.linkText || url || element.label; return (
{element.label} {url ? ( {text} ) : ( - )}
); } function CardStatusRenderer({ element, getQueryResult, queryId, }: { element: CardStatusElement; getQueryResult: (id: string) => QueryResult | null; queryId?: string; }) { const value = getFieldValue(element.columnName, queryId, getQueryResult); const mapping = element.statusMappings?.find((m) => m.value === value); const dotColor = mapping?.color || "#9ca3af"; const label = mapping?.label || value || "-"; return (
{label}
); } function CardStaticTextRenderer({ element }: { element: CardStaticTextElement }) { return (
{element.text}
); } function CardElementRenderer({ element, getQueryResult, queryId, config, }: ElementRendererProps) { switch (element.type) { case "header": return ; case "dataCell": return ( ); case "divider": return ; case "badge": return ( ); case "image": return ; case "number": return ; case "date": return ; case "link": return ; case "status": return ; case "spacer": return
; case "staticText": return ; default: return null; } } // ───────────────────────────────────────────────────────────────────────────── // v3 그리드 렌더러 // ───────────────────────────────────────────────────────────────────────────── function CardGridRenderer({ config, getQueryResult, queryId, }: { config: CardLayoutConfig; getQueryResult: (queryId: string) => QueryResult | null; queryId?: string; }) { return (
0 ? { borderLeft: `${config.accentBorderWidth}px solid ${config.accentBorderColor || "#3b82f6"}` } : {}), }} >
{config.rows.map((row: CardLayoutRow) => (
{row.elements.map((element: CardElement) => ( ))}
))}
); } // ───────────────────────────────────────────────────────────────────────────── // 메인 컴포넌트 // ───────────────────────────────────────────────────────────────────────────── export function CardRenderer({ component, getQueryResult }: CardRendererProps) { const effectiveQueryId = component.queryId || `card_${component.id}`; if (component.cardLayoutConfig) { return ( ); } return ( ); }