o2o-infinith-frontend/src/features/report/components/RoadmapTimeline.tsx
Mina Choi e66b208318 chore: 프론트엔드 마이그레이션 + 아키텍처 정의
- 백엔드 SDK 이전: FastAPI OpenAPI → orval 자동생성 (React Query 훅 포함)
- HTTP 어댑터: ky 기반 customFetcher, fetch httpClient 시그니처
- 아키텍처: features 모듈 / shared 레이어 컨벤션 정의
- 디자인 시스템: Tailwind v4 CSS-first 토큰 (브랜드 색 / status / shadcn)
- 커스텀 CSS: utilities / animations 를 custom.css 로 분리
- Docker: 개발(Dockerfile.dev + compose) / 프로덕션(nginx) 셋업
- README: 기술 스펙 / 프로젝트 구조 / 디자인 토큰 / 폰트 / SDK 사용법 정리

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 11:38:07 +09:00

82 lines
3.2 KiB
TypeScript

import { motion } from 'motion/react';
import { CheckCircle2, Circle } from 'lucide-react';
import { SectionWrapper } from './ui/SectionWrapper';
import type { RoadmapMonth } from '@/features/report/types/report';
interface RoadmapTimelineProps {
months: RoadmapMonth[];
}
const monthMeta: Record<number, { badge: string; accent: string }> = {
1: { badge: 'from-[#6C5CE7] to-[#4F1DA1]', accent: 'border-[#6C5CE7]/20' },
2: { badge: 'from-[#4F1DA1] to-[#021341]', accent: 'border-[#4F1DA1]/20' },
3: { badge: 'from-[#021341] to-[#0A1128]', accent: 'border-[#021341]/20' },
};
export default function RoadmapTimeline({ months }: RoadmapTimelineProps) {
return (
<SectionWrapper id="roadmap" title="90-Day Roadmap" subtitle="실행 로드맵">
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{months.map((month, i) => {
const meta = monthMeta[month.month] || monthMeta[1];
return (
<motion.div
key={month.month}
className={`rounded-2xl bg-white border ${meta.accent} shadow-sm overflow-hidden`}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: i * 0.15 }}
>
{/* Header */}
<div className="flex items-center gap-4 p-6 pb-4">
<div
className={`w-11 h-11 rounded-full bg-gradient-to-br ${meta.badge} text-white flex items-center justify-center font-bold text-sm shrink-0`}
>
{month.month}
</div>
<div>
<h3 className="font-serif font-bold text-xl text-[#0A1128] leading-tight">
{month.title}
</h3>
<p className="text-sm text-slate-500">{month.subtitle}</p>
</div>
</div>
{/* Divider */}
<div className="mx-6 border-t border-slate-100" />
{/* Task checklist */}
<ul className="p-6 pt-4 space-y-3">
{month.tasks.map((task, j) => (
<motion.li
key={j}
className="flex items-start gap-3"
initial={{ opacity: 0, x: -10 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.3, delay: i * 0.15 + j * 0.05 }}
>
{task.completed ? (
<CheckCircle2 size={18} className="text-[#6C5CE7] shrink-0 mt-0.5" />
) : (
<Circle size={18} className="text-slate-300 shrink-0 mt-0.5" />
)}
<span
className={`text-sm leading-relaxed ${
task.completed ? 'text-slate-400 line-through' : 'text-slate-700'
}`}
>
{task.task}
</span>
</motion.li>
))}
</ul>
</motion.div>
);
})}
</div>
</SectionWrapper>
);
}