"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import { reportApi } from "@/lib/api/reportApi"; import { ReportDetail, ReportPage, ReportQuery, WatermarkConfig } from "@/types/report"; export interface QueryResult { queryId: string; fields: string[]; rows: Record[]; } /** * 리포트 데이터 로딩 + 쿼리 실행 훅 * * ReportListPreviewModal의 데이터 로딩 로직을 추출하여 * 모달/인라인 어디서든 재사용 가능하도록 분리. */ export function useReportRenderer( reportId: string | null, contextParams?: Record, ) { const [detail, setDetail] = useState(null); const [queryResults, setQueryResults] = useState([]); const [isLoading, setIsLoading] = useState(false); const getQueryResult = useCallback( (queryId: string): QueryResult | null => { return queryResults.find((r) => r.queryId === queryId) || null; }, [queryResults], ); useEffect(() => { if (!reportId) { setDetail(null); setQueryResults([]); return; } let cancelled = false; setIsLoading(true); (async () => { try { const res = await reportApi.getReportById(reportId); if (cancelled || !res.success) return; setDetail(res.data); // 쿼리 자동 실행 const queries: ReportQuery[] = res.data.queries ?? []; if (queries.length === 0) return; // contextParams에서 키 기반으로 매핑 ($1, $2 등 키를 우선 매칭) const buildParams = (parameters: string[]): Record => { const result: Record = {}; parameters.forEach((param) => { result[param] = contextParams?.[param] ?? null; }); return result; }; const results: QueryResult[] = []; for (const q of queries) { try { const params = buildParams(q.parameters ?? []); const execRes = await reportApi.executeQuery( reportId, q.query_id, params, undefined, // sql_query를 보내지 않고 서버 저장 쿼리 사용 q.external_connection_id, ); if (execRes.success && execRes.data) { results.push({ queryId: q.query_id, fields: execRes.data.fields, rows: execRes.data.rows, }); } } catch { // 개별 쿼리 실패는 무시 } } if (!cancelled) setQueryResults(results); } catch { if (!cancelled) { setDetail(null); setQueryResults([]); } } finally { if (!cancelled) setIsLoading(false); } })(); return () => { cancelled = true; }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [reportId, JSON.stringify(contextParams)]); const { pages, watermark } = useMemo(() => { const empty = { pages: [] as ReportPage[], watermark: undefined as WatermarkConfig | undefined }; if (!detail?.layout) return empty; const layout = detail.layout as unknown as Record; let config: Record | null = null; let raw: unknown = layout.components; while (typeof raw === "string") { try { raw = JSON.parse(raw); } catch { break; } } if (raw && typeof raw === "object" && !Array.isArray(raw)) { config = raw as Record; } if (!config && Array.isArray(layout.pages)) { config = layout; } if (!config) return empty; const foundPages = Array.isArray(config.pages) ? (config.pages as ReportPage[]) : []; const foundWatermark = config.watermark as WatermarkConfig | undefined; return { pages: foundPages, watermark: foundWatermark }; }, [detail?.layout]); return { detail, pages, watermark, queryResults, getQueryResult, isLoading, }; }