Files
vexplor_dev/frontend/hooks/useReportRenderer.ts
kjs ce99001970 Add quote management functionality with API and UI integration
- Introduced a new `quote` module, including routes, controllers, and services for managing quotes.
- Implemented API endpoints for listing, creating, updating, and deleting quotes, ensuring proper company code filtering for data access.
- Developed a comprehensive UI for quote management, allowing users to create, edit, and view quotes seamlessly.
- Enhanced the admin layout to include the new quote management page, improving navigation and accessibility for users.

These additions significantly enhance the application's capabilities in managing quotes, providing users with essential tools for their sales processes.
2026-04-02 15:30:44 +09:00

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,
};
}