140 lines
5.5 KiB
TypeScript
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>
|
|
);
|
|
}
|