o2o-infinith-frontend/src/features/report/components/ReportBody.tsx

140 lines
5.5 KiB
TypeScript

/**
* ReportBody — 리포트 본문(헤더 + 모든 섹션) 순수 렌더링.
*
* 각 섹션은 데이터가 비어있으면 `EmptySection` 으로 fallback 되어
* "데이터가 없습니다" 안내가 표시됩니다. Guest / User 두 페이지 모두
* 동일한 동작.
*/
import type { MarketingReport } from '@/features/report/types/report';
import { SectionErrorBoundary } from '@/features/report/components/ui/SectionErrorBoundary';
import { EmptySection } from '@/features/report/components/ui/EmptySection';
import ReportHeader from '@/features/report/components/ReportHeader';
import ClinicSnapshot from '@/features/report/components/ClinicSnapshot';
import ChannelOverview from '@/features/report/components/ChannelOverview';
import YouTubeAudit from '@/features/report/components/YouTubeAudit';
import InstagramAudit from '@/features/report/components/InstagramAudit';
import FacebookAudit from '@/features/report/components/FacebookAudit';
import OtherChannels from '@/features/report/components/OtherChannels';
import ProblemDiagnosis from '@/features/report/components/ProblemDiagnosis';
import TransformationProposal from '@/features/report/components/TransformationProposal';
import RoadmapTimeline from '@/features/report/components/RoadmapTimeline';
import KPIDashboard from '@/features/report/components/KPIDashboard';
interface ReportBodyProps {
data: MarketingReport;
}
function hasValue<T>(v: T | null | undefined): v is T {
return v != null;
}
function nonEmpty<T>(arr: T[] | null | undefined): arr is T[] {
return Array.isArray(arr) && arr.length > 0;
}
export default function ReportBody({ data }: ReportBodyProps) {
// 각 audit에서 핸들 끌어와 헤더의 바로가기 버튼에 전달
const socialHandles = {
website: data.targetUrl || data.clinicSnapshot.domain || null,
youtube: data.youtubeAudit?.handle || null,
instagram: data.instagramAudit?.accounts?.[0]?.handle || null,
facebook: data.facebookAudit?.pages?.[0]?.url || data.facebookAudit?.pages?.[0]?.pageName || null,
};
return (
<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}
socialHandles={socialHandles}
/>
<SectionErrorBoundary>
{hasValue(data.clinicSnapshot) && data.clinicSnapshot.name ? (
<ClinicSnapshot data={data.clinicSnapshot} />
) : (
<EmptySection id="clinic-snapshot" title="Clinic Overview" subtitle="병원 기본 정보" />
)}
</SectionErrorBoundary>
<SectionErrorBoundary>
{nonEmpty(data.channelScores) ? (
<ChannelOverview channels={data.channelScores} />
) : (
<EmptySection id="channel-overview" title="Channel Overview" subtitle="채널 종합 진단" />
)}
</SectionErrorBoundary>
<SectionErrorBoundary>
{hasValue(data.youtubeAudit) && data.youtubeAudit.handle ? (
<YouTubeAudit data={data.youtubeAudit} />
) : (
<EmptySection id="youtube-audit" title="YouTube Analysis" subtitle="유튜브 채널 분석" />
)}
</SectionErrorBoundary>
<SectionErrorBoundary>
{hasValue(data.instagramAudit) && nonEmpty(data.instagramAudit.accounts) ? (
<InstagramAudit data={data.instagramAudit} />
) : (
<EmptySection id="instagram-audit" title="Instagram Analysis" subtitle="인스타그램 분석" />
)}
</SectionErrorBoundary>
<SectionErrorBoundary>
{hasValue(data.facebookAudit) && nonEmpty(data.facebookAudit.pages) ? (
<FacebookAudit data={data.facebookAudit} />
) : (
<EmptySection id="facebook-audit" title="Facebook Analysis" subtitle="페이스북 페이지 분석" />
)}
</SectionErrorBoundary>
<SectionErrorBoundary>
{nonEmpty(data.otherChannels) || hasValue(data.websiteAudit) ? (
<OtherChannels channels={data.otherChannels ?? []} website={data.websiteAudit} />
) : (
<EmptySection id="other-channels" title="Other Channels" subtitle="기타 채널 및 웹사이트" />
)}
</SectionErrorBoundary>
<SectionErrorBoundary>
{nonEmpty(data.problemDiagnosis) ? (
<ProblemDiagnosis diagnosis={data.problemDiagnosis} />
) : (
<EmptySection id="problem-diagnosis" title="Problem Diagnosis" subtitle="문제 진단" />
)}
</SectionErrorBoundary>
<SectionErrorBoundary>
{hasValue(data.transformation) ? (
<TransformationProposal data={data.transformation} />
) : (
<EmptySection id="transformation" title="Transformation" subtitle="개선 제안" />
)}
</SectionErrorBoundary>
<SectionErrorBoundary>
{nonEmpty(data.roadmap) ? (
<RoadmapTimeline months={data.roadmap} />
) : (
<EmptySection id="roadmap" title="Roadmap" subtitle="실행 로드맵" />
)}
</SectionErrorBoundary>
<SectionErrorBoundary>
{nonEmpty(data.kpiDashboard) ? (
<KPIDashboard metrics={data.kpiDashboard} clinicName={data.clinicSnapshot.name} />
) : (
<EmptySection id="kpi-dashboard" title="KPI Dashboard" subtitle="핵심 지표" />
)}
</SectionErrorBoundary>
</div>
);
}