134 lines
4.2 KiB
TypeScript
134 lines
4.2 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useEffect, useCallback } from "react";
|
|
import { ComponentRendererProps } from "@/types/component";
|
|
import { ReportParamMapping, ReportViewerConfig } from "./types";
|
|
import { useScreenContextOptional } from "@/contexts/ScreenContext";
|
|
import { reportApi } from "@/lib/api/reportApi";
|
|
import { ReportMaster } from "@/types/report";
|
|
import { ReportListPreviewModal } from "@/components/report/ReportListPreviewModal";
|
|
import { FileText, Loader2 } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
/**
|
|
* paramMappings가 있으면 명시적 매핑, 없으면 formData 그대로 전달 (휴리스틱 유지)
|
|
*/
|
|
function buildContextParams(
|
|
formData: Record<string, unknown>,
|
|
mappings: ReportParamMapping[],
|
|
): Record<string, unknown> {
|
|
if (mappings.length === 0) return formData;
|
|
|
|
const result: Record<string, unknown> = {};
|
|
for (const { param, formField } of mappings) {
|
|
if (param && formField) {
|
|
result[param] = formData[formField] ?? null;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
interface ReportViewerComponentProps extends ComponentRendererProps {
|
|
config?: ReportViewerConfig;
|
|
formData?: Record<string, any>;
|
|
}
|
|
|
|
export const ReportViewerComponent: React.FC<ReportViewerComponentProps> = ({
|
|
component,
|
|
isDesignMode = false,
|
|
formData,
|
|
}) => {
|
|
const screenContext = useScreenContextOptional();
|
|
const menuObjid = screenContext?.menuObjid;
|
|
const contextFormData = screenContext?.formData ?? formData ?? {};
|
|
|
|
const config = (component?.componentConfig ?? component?.overrides ?? {}) as ReportViewerConfig;
|
|
const title = config.title || "리포트";
|
|
const paramMappings = config.paramMappings ?? [];
|
|
|
|
const resolvedContextParams = buildContextParams(contextFormData, paramMappings);
|
|
|
|
const [reports, setReports] = useState<ReportMaster[]>([]);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [selectedReport, setSelectedReport] = useState<ReportMaster | null>(null);
|
|
|
|
const fetchReports = useCallback(async () => {
|
|
if (!menuObjid) return;
|
|
setIsLoading(true);
|
|
try {
|
|
const res = await reportApi.getReportsByMenuObjid(menuObjid);
|
|
if (res.success) setReports(res.data.items);
|
|
} catch {
|
|
// 실패 시 빈 목록 유지
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [menuObjid]);
|
|
|
|
useEffect(() => {
|
|
if (isDesignMode) return;
|
|
fetchReports();
|
|
}, [isDesignMode, fetchReports]);
|
|
|
|
// 디자인 모드: 플레이스홀더
|
|
if (isDesignMode) {
|
|
return (
|
|
<div className="flex h-full w-full flex-col items-center justify-center gap-2 rounded border border-dashed border-blue-300 bg-blue-50 text-blue-600">
|
|
<FileText className="h-8 w-8 opacity-60" />
|
|
<span className="text-sm font-medium">{title} (리포트 뷰어)</span>
|
|
<span className="text-xs opacity-60">실행 시 메뉴에 연결된 리포트 목록이 표시됩니다</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// menuObjid 없음
|
|
if (!menuObjid) {
|
|
return (
|
|
<div className="flex h-full w-full items-center justify-center text-sm text-gray-400">
|
|
연결된 메뉴가 없습니다
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex h-full w-full items-center justify-center">
|
|
<Loader2 className="h-6 w-6 animate-spin text-gray-400" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (reports.length === 0) {
|
|
return (
|
|
<div className="flex h-full w-full flex-col items-center justify-center gap-2 text-sm text-gray-400">
|
|
<FileText className="h-8 w-8 opacity-30" />
|
|
<span>연결된 리포트가 없습니다</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<div className="flex h-full w-full flex-col gap-1 overflow-auto p-2">
|
|
{reports.map((report) => (
|
|
<Button
|
|
key={report.report_id}
|
|
variant="outline"
|
|
className="w-full justify-start gap-2 text-left text-sm"
|
|
onClick={() => setSelectedReport(report)}
|
|
>
|
|
<FileText className="h-4 w-4 shrink-0 text-blue-500" />
|
|
<span className="truncate">{report.report_name_kor}</span>
|
|
</Button>
|
|
))}
|
|
</div>
|
|
|
|
<ReportListPreviewModal
|
|
report={selectedReport}
|
|
onClose={() => setSelectedReport(null)}
|
|
contextParams={resolvedContextParams}
|
|
/>
|
|
</>
|
|
);
|
|
};
|