o2o-infinith-demo/src/components/plan/ContentStrategy.tsx

224 lines
8.7 KiB
TypeScript

import { useState } from 'react';
import { motion } from 'motion/react';
import { ArrowRight } from 'lucide-react';
import { VideoFilled } from '../icons/FilledIcons';
import { SectionWrapper } from '../report/ui/SectionWrapper';
import type { ContentStrategyData } from '../../types/plan';
interface ContentStrategyProps {
data: ContentStrategyData;
}
const tabItems = [
{ key: 'pillars', label: 'Content Pillars', labelKr: '콘텐츠 필러' },
{ key: 'types', label: 'Content Types', labelKr: '콘텐츠 유형' },
{ key: 'workflow', label: 'Production Workflow', labelKr: '제작 워크플로우' },
{ key: 'repurposing', label: 'Repurposing', labelKr: '콘텐츠 재활용' },
] as const;
type TabKey = (typeof tabItems)[number]['key'];
const channelColorMap: Record<string, string> = {
YouTube: 'bg-[#FFF0F0] text-[#7C3A4B]',
Instagram: 'bg-[#FFF0F0] text-[#7C3A4B]',
Blog: 'bg-[#EFF0FF] text-[#3A3F7C]',
'블로그': 'bg-[#EFF0FF] text-[#3A3F7C]',
'네이버블로그': 'bg-[#F3F0FF] text-[#4A3A7C]',
'네이버': 'bg-[#F3F0FF] text-[#4A3A7C]',
Facebook: 'bg-[#EFF0FF] text-[#3A3F7C]',
'홈페이지': 'bg-slate-100 text-slate-700',
Website: 'bg-slate-100 text-slate-700',
TikTok: 'bg-[#F3F0FF] text-[#4A3A7C]',
'카카오': 'bg-[#FFF6ED] text-[#7C5C3A]',
};
function getChannelBadgeClass(channel: string): string {
for (const [key, value] of Object.entries(channelColorMap)) {
if (channel.toLowerCase().includes(key.toLowerCase())) return value;
}
return 'bg-slate-100 text-slate-700';
}
export default function ContentStrategy({ data }: ContentStrategyProps) {
const [activeTab, setActiveTab] = useState<TabKey>('pillars');
return (
<SectionWrapper
id="content-strategy"
title="Content Strategy"
subtitle="콘텐츠 마케팅 전략"
>
{/* Tabs */}
<div className="flex flex-wrap gap-2 mb-8">
{tabItems.map((tab) => (
<button
key={tab.key}
onClick={() => setActiveTab(tab.key)}
className={`rounded-full px-4 py-2 text-sm font-medium transition-all ${
activeTab === tab.key
? 'bg-gradient-to-r from-[#4F1DA1] to-[#021341] text-white shadow-lg'
: 'bg-white border border-slate-200 text-slate-600 hover:bg-slate-50'
}`}
>
{tab.label}
</button>
))}
</div>
{/* Tab 1: Content Pillars */}
{activeTab === 'pillars' && (
<motion.div
className="grid md:grid-cols-2 gap-6"
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }}
>
{data.pillars.map((pillar, i) => (
<motion.div
key={pillar.title}
className="rounded-2xl border border-slate-100 bg-white shadow-sm p-6 border-l-4"
style={{ borderLeftColor: pillar.color }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: i * 0.1 }}
>
<h4 className="font-serif text-xl font-bold text-[#0A1128] mb-2">
{pillar.title}
</h4>
<p className="text-sm text-slate-600 mb-3">{pillar.description}</p>
<span className="inline-block rounded-full bg-slate-100 px-3 py-1 text-xs font-medium text-slate-700 mb-4">
{pillar.relatedUSP}
</span>
<ul className="space-y-2">
{pillar.exampleTopics.map((topic, j) => (
<li key={j} className="flex items-start gap-2 text-sm text-slate-700">
<span
className="shrink-0 w-2 h-2 rounded-full mt-2"
style={{ backgroundColor: pillar.color }}
/>
{topic}
</li>
))}
</ul>
</motion.div>
))}
</motion.div>
)}
{/* Tab 2: Content Types */}
{activeTab === 'types' && (
<motion.div
className="rounded-2xl overflow-hidden border border-slate-100"
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }}
>
<div className="grid grid-cols-4 bg-[#0A1128] text-white">
<div className="px-6 py-4 text-sm font-semibold">Format</div>
<div className="px-6 py-4 text-sm font-semibold">Channels</div>
<div className="px-6 py-4 text-sm font-semibold">Frequency</div>
<div className="px-6 py-4 text-sm font-semibold">Purpose</div>
</div>
{data.typeMatrix.map((row, i) => (
<motion.div
key={row.format}
className={`grid grid-cols-4 ${i % 2 === 0 ? 'bg-white' : 'bg-slate-50'}`}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, delay: i * 0.04 }}
>
<div className="px-6 py-4 text-sm font-medium text-[#0A1128]">
{row.format}
</div>
<div className="px-6 py-4 flex flex-wrap gap-2">
{row.channels.map((ch) => (
<span
key={ch}
className={`inline-flex items-center rounded-full px-3 py-1 text-xs font-medium ${getChannelBadgeClass(ch)}`}
>
{ch}
</span>
))}
</div>
<div className="px-6 py-4 text-sm text-slate-700">{row.frequency}</div>
<div className="px-6 py-4 text-sm text-slate-600">{row.purpose}</div>
</motion.div>
))}
</motion.div>
)}
{/* Tab 3: Production Workflow */}
{activeTab === 'workflow' && (
<motion.div
className="flex md:flex-row flex-col gap-4 items-stretch"
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }}
>
{data.workflow.map((step, i) => (
<div key={step.step} className="flex md:flex-row flex-col items-center gap-4 flex-1">
<motion.div
className="flex-1 w-full rounded-2xl border border-slate-100 bg-white shadow-sm p-5"
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: i * 0.1 }}
>
<div className="w-10 h-10 rounded-full bg-gradient-to-r from-[#4F1DA1] to-[#021341] text-white font-bold flex items-center justify-center mb-3">
{step.step}
</div>
<h4 className="font-semibold text-[#0A1128] mb-1">{step.name}</h4>
<p className="text-sm text-slate-600 mb-3">{step.description}</p>
<div className="flex flex-wrap gap-2">
<span className="rounded-full bg-[#F3F0FF] text-[#4A3A7C] px-2 py-1 text-xs">
{step.owner}
</span>
<span className="rounded-full bg-slate-100 text-slate-600 px-2 py-1 text-xs">
{step.duration}
</span>
</div>
</motion.div>
{i < data.workflow.length - 1 && (
<ArrowRight
size={20}
className="text-slate-300 shrink-0 hidden md:block"
/>
)}
</div>
))}
</motion.div>
)}
{/* Tab 4: Repurposing */}
{activeTab === 'repurposing' && (
<motion.div
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }}
>
{/* Source Card */}
<div className="rounded-2xl bg-gradient-to-r from-[#4F1DA1] to-[#021341] p-6 text-white mb-6">
<div className="flex items-center gap-3">
<VideoFilled size={28} className="text-white/60" />
<h4 className="font-serif text-xl font-bold">{data.repurposingSource}</h4>
</div>
</div>
{/* Connector */}
<div className="flex justify-center mb-6">
<div className="w-px h-8 bg-gradient-to-b from-[#4F1DA1] to-slate-200" />
</div>
{/* Outputs Grid */}
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-3">
{data.repurposingOutputs.map((output, i) => (
<motion.div
key={`${output.format}-${i}`}
className="rounded-xl border border-slate-100 bg-white p-4"
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, delay: i * 0.05 }}
>
<p className="font-semibold text-sm text-[#0A1128] mb-1">{output.format}</p>
<p className="text-xs text-slate-500 mb-1">{output.channel}</p>
<p className="text-xs text-slate-600">{output.description}</p>
</motion.div>
))}
</div>
</motion.div>
)}
</SectionWrapper>
);
}