o2o-infinith-frontend/src/features/report/components/ReportHeader.tsx

178 lines
6.7 KiB
TypeScript

import { motion } from 'motion/react';
import { Calendar, Globe, MapPin } from 'lucide-react';
import { ScoreRing } from './ui/ScoreRing';
import { ChannelLinkButtons, type ChannelHandles } from '@/shared/ui/channel-link-buttons';
import { PageContainer } from '@/shared/ui/page-container';
function formatDate(raw: string): string {
try {
return new Date(raw).toLocaleDateString('ko-KR', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
} catch {
return raw;
}
}
interface ReportHeaderProps {
clinicName: string;
clinicNameEn: string;
overallScore: number;
date: string;
targetUrl: string;
location: string;
logoImage?: string;
brandColors?: { primary: string; accent: string; text: string };
/** 등록된 채널 핸들/URL — 외부 새 탭으로 이동 버튼 묶음을 렌더링 */
socialHandles?: ChannelHandles;
}
export default function ReportHeader({
clinicName,
logoImage,
brandColors,
clinicNameEn,
overallScore,
date,
targetUrl,
location,
socialHandles,
}: ReportHeaderProps) {
return (
<section className="relative overflow-hidden bg-[radial-gradient(ellipse_at_top_left,#e0e7ff,transparent_50%),radial-gradient(ellipse_at_bottom_right,#fce7f3,transparent_50%),radial-gradient(ellipse_at_center,#f5f3ff,transparent_60%)] py-20 px-6">
{/* Animated blobs */}
<motion.div
className="absolute top-10 left-10 w-72 h-72 rounded-full bg-indigo-200/30 blur-3xl"
animate={{ x: [0, 30, 0], y: [0, -20, 0] }}
transition={{ duration: 8, repeat: Infinity, ease: 'easeInOut' }}
/>
<motion.div
className="absolute bottom-10 right-10 w-96 h-96 rounded-full bg-pink-200/30 blur-3xl"
animate={{ x: [0, -20, 0], y: [0, 30, 0] }}
transition={{ duration: 10, repeat: Infinity, ease: 'easeInOut' }}
/>
<motion.div
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-80 h-80 rounded-full bg-purple-200/20 blur-3xl"
animate={{ scale: [1, 1.1, 1] }}
transition={{ duration: 6, repeat: Infinity, ease: 'easeInOut' }}
/>
<PageContainer className="relative px-0">
<div className="flex flex-col md:flex-row items-center md:items-start justify-between gap-10">
{/* Left: Text content */}
<motion.div
className="flex-1 text-center md:text-left"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
>
<motion.p
className="font-serif text-3xl md:text-4xl font-normal text-[#6C5CE7] mb-4 tracking-wide"
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.1 }}
>
Marketing Intelligence Report
</motion.p>
{logoImage && (
<motion.div
className="mb-4"
initial={{ opacity: 0, scale: 0.9 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.15 }}
>
<img
src={logoImage}
alt={clinicName}
className="h-16 md:h-20 w-auto object-contain md:mx-0 mx-auto"
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }}
/>
</motion.div>
)}
<motion.h1
className="font-serif text-4xl md:text-5xl font-bold text-[#0A1128] mb-3"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.2 }}
>
{clinicName}
</motion.h1>
<motion.p
className="text-lg text-slate-600 mb-8"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.3 }}
>
{clinicNameEn}
</motion.p>
<motion.div
className="flex flex-wrap gap-3 justify-center md:justify-start"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.4 }}
>
<span className="inline-flex items-center gap-2 rounded-full bg-white/60 backdrop-blur-sm border border-white/40 px-3 py-1 text-sm font-medium text-slate-700">
<Calendar size={14} className="text-slate-400" />
{formatDate(date)}
</span>
<span className="inline-flex items-center gap-2 rounded-full bg-white/60 backdrop-blur-sm border border-white/40 px-3 py-1 text-sm font-medium text-slate-700">
<Globe size={14} className="text-slate-400" />
{targetUrl}
</span>
<span className="inline-flex items-center gap-2 rounded-full bg-white/60 backdrop-blur-sm border border-white/40 px-3 py-1 text-sm font-medium text-slate-700">
<MapPin size={14} className="text-slate-400" />
{location}
</span>
</motion.div>
{/* 등록된 채널 바로가기 */}
{socialHandles && (
<motion.div
className="mt-4"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.5 }}
>
<ChannelLinkButtons
handles={socialHandles}
variant="light"
className="justify-center md:justify-start"
/>
</motion.div>
)}
</motion.div>
{/* Right: Score ring */}
<motion.div
className="shrink-0"
initial={{ opacity: 0, scale: 0.8 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.3 }}
>
<div className="bg-white/60 backdrop-blur-sm border border-white/40 rounded-3xl p-8 shadow-lg">
<p className="text-xs text-slate-500 uppercase tracking-wide text-center mb-4">
Overall Score
</p>
<ScoreRing score={overallScore} size={160} label="종합 점수" />
</div>
</motion.div>
</div>
</PageContainer>
</section>
);
}