158 lines
6.3 KiB
TypeScript
158 lines
6.3 KiB
TypeScript
import { useMemo } from 'react';
|
|
import { useParams, useLocation } from 'react-router';
|
|
import { useReport } from '../hooks/useReport';
|
|
import { useEnrichment } from '../hooks/useEnrichment';
|
|
import { ReportNav } from '../components/report/ReportNav';
|
|
import { ScreenshotProvider } from '../contexts/ScreenshotContext';
|
|
|
|
// Report section components
|
|
import { SectionErrorBoundary } from '../components/report/ui/SectionErrorBoundary';
|
|
import ReportHeader from '../components/report/ReportHeader';
|
|
import ClinicSnapshot from '../components/report/ClinicSnapshot';
|
|
import ChannelOverview from '../components/report/ChannelOverview';
|
|
import YouTubeAudit from '../components/report/YouTubeAudit';
|
|
import InstagramAudit from '../components/report/InstagramAudit';
|
|
import FacebookAudit from '../components/report/FacebookAudit';
|
|
import OtherChannels from '../components/report/OtherChannels';
|
|
import ProblemDiagnosis from '../components/report/ProblemDiagnosis';
|
|
import TransformationProposal from '../components/report/TransformationProposal';
|
|
import RoadmapTimeline from '../components/report/RoadmapTimeline';
|
|
import KPIDashboard from '../components/report/KPIDashboard';
|
|
|
|
const REPORT_SECTIONS = [
|
|
{ id: 'header', label: '개요' },
|
|
{ id: 'clinic-snapshot', label: '의원 현황' },
|
|
{ id: 'channel-overview', label: '채널 종합' },
|
|
{ id: 'youtube-audit', label: 'YouTube' },
|
|
{ id: 'instagram-audit', label: 'Instagram' },
|
|
{ id: 'facebook-audit', label: 'Facebook' },
|
|
{ id: 'other-channels', label: '기타 채널' },
|
|
{ id: 'problem-diagnosis', label: '문제 진단' },
|
|
{ id: 'roadmap', label: '로드맵' },
|
|
{ id: 'kpi-dashboard', label: 'KPI' },
|
|
];
|
|
|
|
export default function ReportPage() {
|
|
const { id } = useParams<{ id: string }>();
|
|
const location = useLocation();
|
|
const {
|
|
data: baseData,
|
|
isLoading,
|
|
error,
|
|
isEnriched,
|
|
socialHandles: dbSocialHandles,
|
|
} = useReport(id);
|
|
|
|
// Build enrichment params — skip if already enriched (data from DB)
|
|
const enrichmentParams = useMemo(() => {
|
|
if (!baseData || isEnriched) return null;
|
|
|
|
// Priority: location.state socialHandles > DB socialHandles > transformed data
|
|
const state = location.state as Record<string, unknown> | undefined;
|
|
const metadata = state?.metadata as Record<string, unknown> | undefined;
|
|
const stateSocialHandles = metadata?.socialHandles as Record<string, string | null> | undefined;
|
|
|
|
const handles = stateSocialHandles || dbSocialHandles;
|
|
|
|
// Instagram: support array of handles (multi-account) or single handle
|
|
const igHandles: string[] = Array.isArray(handles?.instagram)
|
|
? handles.instagram.filter(Boolean) as string[]
|
|
: handles?.instagram ? [handles.instagram as string] : [];
|
|
|
|
const ytHandle =
|
|
handles?.youtube ||
|
|
baseData.youtubeAudit?.handle ||
|
|
undefined;
|
|
|
|
const fbHandle = handles?.facebook || undefined;
|
|
|
|
return {
|
|
reportId: baseData.id,
|
|
clinicName: baseData.clinicSnapshot.name,
|
|
instagramHandles: igHandles.length > 0 ? igHandles : undefined,
|
|
youtubeChannelId: ytHandle || undefined,
|
|
facebookHandle: fbHandle as string | undefined,
|
|
address: baseData.clinicSnapshot.location || undefined,
|
|
};
|
|
}, [baseData, isEnriched, dbSocialHandles, location.state]);
|
|
|
|
const { status: enrichStatus, enrichedReport } = useEnrichment(baseData, enrichmentParams);
|
|
|
|
// Use enriched data when available, otherwise base data
|
|
const data = enrichedReport || baseData;
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center pt-20">
|
|
<div className="flex flex-col items-center gap-4">
|
|
<div className="w-10 h-10 border-4 border-[#6C5CE7] border-t-transparent rounded-full animate-spin" />
|
|
<p className="text-slate-500 text-sm">리포트를 불러오는 중...</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (error || !data) {
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center pt-20">
|
|
<div className="text-center">
|
|
<p className="text-[#7C3A4B] font-medium mb-2">오류가 발생했습니다</p>
|
|
<p className="text-slate-500 text-sm">{error ?? '리포트를 찾을 수 없습니다.'}</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<ScreenshotProvider screenshots={data.screenshots ?? []}>
|
|
<div className="pt-20">
|
|
<ReportNav sections={REPORT_SECTIONS} />
|
|
|
|
{/* Enrichment status indicator */}
|
|
{enrichStatus === 'loading' && (
|
|
<div className="fixed bottom-6 right-6 z-50 flex items-center gap-3 px-4 py-3 bg-white rounded-xl shadow-[3px_4px_12px_rgba(0,0,0,0.06)] border border-slate-100">
|
|
<div className="w-4 h-4 border-2 border-[#6C5CE7] border-t-transparent rounded-full animate-spin" />
|
|
<span className="text-xs text-slate-500">채널 데이터 보강 중...</span>
|
|
</div>
|
|
)}
|
|
|
|
<div data-report-content>
|
|
<ReportHeader
|
|
overallScore={data.overallScore}
|
|
clinicName={data.clinicSnapshot.name}
|
|
clinicNameEn={data.clinicSnapshot.nameEn}
|
|
targetUrl={data.targetUrl}
|
|
date={data.createdAt}
|
|
location={data.clinicSnapshot.location}
|
|
logoImage={data.clinicSnapshot.logoImages?.horizontal}
|
|
brandColors={data.clinicSnapshot.brandColors}
|
|
/>
|
|
|
|
<SectionErrorBoundary><ClinicSnapshot data={data.clinicSnapshot} /></SectionErrorBoundary>
|
|
|
|
<SectionErrorBoundary><ChannelOverview channels={data.channelScores} /></SectionErrorBoundary>
|
|
|
|
<SectionErrorBoundary><YouTubeAudit data={data.youtubeAudit} /></SectionErrorBoundary>
|
|
|
|
<SectionErrorBoundary><InstagramAudit data={data.instagramAudit} /></SectionErrorBoundary>
|
|
|
|
<SectionErrorBoundary><FacebookAudit data={data.facebookAudit} /></SectionErrorBoundary>
|
|
|
|
<SectionErrorBoundary><OtherChannels
|
|
channels={data.otherChannels}
|
|
website={data.websiteAudit}
|
|
/></SectionErrorBoundary>
|
|
|
|
<SectionErrorBoundary><ProblemDiagnosis diagnosis={data.problemDiagnosis} /></SectionErrorBoundary>
|
|
|
|
<SectionErrorBoundary><TransformationProposal data={data.transformation} /></SectionErrorBoundary>
|
|
|
|
<SectionErrorBoundary><RoadmapTimeline months={data.roadmap} /></SectionErrorBoundary>
|
|
|
|
<SectionErrorBoundary><KPIDashboard metrics={data.kpiDashboard} clinicName={data.clinicSnapshot.name} /></SectionErrorBoundary>
|
|
</div>
|
|
</div>
|
|
</ScreenshotProvider>
|
|
);
|
|
}
|