o2o-infinith-demo/src/pages/ReportPage.tsx

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