119 lines
4.2 KiB
TypeScript
119 lines
4.2 KiB
TypeScript
import { useEffect } from 'react';
|
|
import { useParams, useLocation } from 'react-router';
|
|
import { useMarketingPlan } from '../hooks/useMarketingPlan';
|
|
import { ReportNav } from '../components/report/ReportNav';
|
|
|
|
// Plan section components
|
|
import PlanHeader from '../components/plan/PlanHeader';
|
|
import BrandingGuide from '../components/plan/BrandingGuide';
|
|
import ChannelStrategy from '../components/plan/ChannelStrategy';
|
|
import ContentStrategy from '../components/plan/ContentStrategy';
|
|
import ContentCalendar from '../components/plan/ContentCalendar';
|
|
import AssetCollection from '../components/plan/AssetCollection';
|
|
import RepurposingProposal from '../components/plan/RepurposingProposal';
|
|
import MyAssetUpload from '../components/plan/MyAssetUpload';
|
|
import StrategyAdjustmentSection from '../components/plan/StrategyAdjustmentSection';
|
|
import WorkflowTracker from '../components/plan/WorkflowTracker';
|
|
import PlanCTA from '../components/plan/PlanCTA';
|
|
|
|
const PLAN_SECTIONS = [
|
|
{ id: 'branding-guide', label: '브랜딩 가이드' },
|
|
{ id: 'channel-strategy', label: '채널 전략' },
|
|
{ id: 'content-strategy', label: '콘텐츠 전략' },
|
|
{ id: 'content-calendar', label: '콘텐츠 캘린더' },
|
|
{ id: 'asset-collection', label: '에셋 수집' },
|
|
{ id: 'repurposing-proposal', label: '리퍼포징 제안' },
|
|
{ id: 'workflow-tracker', label: '제작 파이프라인' },
|
|
{ id: 'my-asset-upload', label: '나의 소재' },
|
|
{ id: 'strategy-adjustment', label: '전략 조정' },
|
|
];
|
|
|
|
export default function MarketingPlanPage() {
|
|
const { id } = useParams<{ id: string }>();
|
|
const location = useLocation();
|
|
const clinicId = (location.state as { clinicId?: string } | undefined)?.clinicId || null;
|
|
const { data, isLoading, error } = useMarketingPlan(id);
|
|
|
|
// Hash-based scroll: /plan/:id#section-id → scroll to that section after render
|
|
// Offsets sticky Navbar (80px) + ReportNav (~48px) so the section top isn't covered.
|
|
useEffect(() => {
|
|
if (isLoading || !location.hash) return;
|
|
const sectionId = location.hash.slice(1);
|
|
const timer = setTimeout(() => {
|
|
const el = document.getElementById(sectionId);
|
|
if (!el) return;
|
|
const STICKY_OFFSET = 128;
|
|
const y = el.getBoundingClientRect().top + window.scrollY - STICKY_OFFSET;
|
|
window.scrollTo({ top: y, behavior: 'smooth' });
|
|
}, 300);
|
|
return () => clearTimeout(timer);
|
|
}, [isLoading, location.hash]);
|
|
|
|
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 (
|
|
<div className="pt-20">
|
|
<ReportNav sections={PLAN_SECTIONS} />
|
|
|
|
<div data-plan-content>
|
|
<PlanHeader
|
|
clinicName={data.clinicName}
|
|
clinicNameEn={data.clinicNameEn}
|
|
date={data.createdAt}
|
|
targetUrl={data.targetUrl}
|
|
/>
|
|
|
|
<BrandingGuide data={data.brandGuide} />
|
|
|
|
<ChannelStrategy channels={data.channelStrategies} />
|
|
|
|
<ContentStrategy data={data.contentStrategy} />
|
|
|
|
<ContentCalendar data={data.calendar} />
|
|
|
|
<AssetCollection data={data.assetCollection} />
|
|
|
|
{data.repurposingProposals && data.repurposingProposals.length > 0 && (
|
|
<RepurposingProposal proposals={data.repurposingProposals} />
|
|
)}
|
|
|
|
{data.workflow && (
|
|
<WorkflowTracker data={data.workflow} />
|
|
)}
|
|
|
|
<div data-no-print>
|
|
<MyAssetUpload />
|
|
</div>
|
|
|
|
<div data-no-print>
|
|
<StrategyAdjustmentSection clinicId={clinicId} planId={data.id} />
|
|
</div>
|
|
|
|
<PlanCTA />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|