147 lines
4.0 KiB
TypeScript
147 lines
4.0 KiB
TypeScript
|
|
"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<string, unknown>[];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 리포트 데이터 로딩 + 쿼리 실행 훅
|
||
|
|
*
|
||
|
|
* ReportListPreviewModal의 데이터 로딩 로직을 추출하여
|
||
|
|
* 모달/인라인 어디서든 재사용 가능하도록 분리.
|
||
|
|
*/
|
||
|
|
export function useReportRenderer(
|
||
|
|
reportId: string | null,
|
||
|
|
contextParams?: Record<string, unknown>,
|
||
|
|
) {
|
||
|
|
const [detail, setDetail] = useState<ReportDetail | null>(null);
|
||
|
|
const [queryResults, setQueryResults] = useState<QueryResult[]>([]);
|
||
|
|
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<string, unknown> => {
|
||
|
|
const result: Record<string, unknown> = {};
|
||
|
|
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<string, unknown>;
|
||
|
|
|
||
|
|
let config: Record<string, unknown> | 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<string, unknown>;
|
||
|
|
}
|
||
|
|
|
||
|
|
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,
|
||
|
|
};
|
||
|
|
}
|