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

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