ERP-node/frontend/lib/registry/components/v2-report-viewer/ReportViewerComponent.tsx

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}
/>
</>
);
};