o2o-infinith-demo/src/components/report/ClinicSnapshot.tsx

115 lines
4.7 KiB
TypeScript

import { motion } from 'motion/react';
import { Calendar, Users, MapPin, Phone, Award, Star, Globe } from 'lucide-react';
import { SectionWrapper } from './ui/SectionWrapper';
import type { ClinicSnapshot as ClinicSnapshotType } from '../../types/report';
interface ClinicSnapshotProps {
data: ClinicSnapshotType;
}
function formatNumber(n: number): string {
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(2)}M`;
if (n >= 1_000) return `${(n / 1_000).toFixed(0)}K`;
return n.toLocaleString();
}
const infoFields = (data: ClinicSnapshotType) => [
{ label: '개원', value: `${data.established} (${data.yearsInBusiness}년)`, icon: Calendar },
{ label: '의료진', value: `${data.staffCount}`, icon: Users },
{ label: '강남언니 평점', value: `${data.overallRating} / 5.0`, icon: Star },
{ label: '리뷰 수', value: formatNumber(data.totalReviews), icon: Star },
{ label: '시술 가격대', value: `${data.priceRange.min} ~ ${data.priceRange.max}`, icon: Globe },
{ label: '위치', value: `${data.location} (${data.nearestStation})`, icon: MapPin },
{ label: '전화', value: data.phone, icon: Phone },
{ label: '도메인', value: data.domain, icon: Globe },
];
export default function ClinicSnapshot({ data }: ClinicSnapshotProps) {
const fields = infoFields(data);
return (
<SectionWrapper id="clinic-snapshot" title="Clinic Overview" subtitle="병원 기본 정보">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8">
{fields.map((field, i) => {
const Icon = field.icon;
return (
<motion.div
key={field.label}
className="rounded-2xl bg-white border border-slate-100 shadow-sm p-5"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: i * 0.05 }}
>
<div className="flex items-start gap-3">
<div className="shrink-0 w-8 h-8 rounded-lg bg-slate-50 flex items-center justify-center">
<Icon size={16} className="text-slate-400" />
</div>
<div className="min-w-0">
<p className="text-xs text-slate-500 uppercase tracking-wide">{field.label}</p>
<p className="text-lg font-semibold text-[#0A1128] mt-1">{field.value}</p>
</div>
</div>
</motion.div>
);
})}
</div>
{/* Lead Doctor Highlight */}
<motion.div
className="rounded-2xl bg-gradient-to-r from-[#4F1DA1]/5 to-[#021341]/5 border border-purple-100 p-6 mb-8"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.3 }}
>
<div className="flex items-center gap-2 mb-3">
<Award size={20} className="text-[#6C5CE7]" />
<h3 className="font-serif font-bold text-2xl text-[#0A1128]"> </h3>
</div>
<p className="text-xl font-bold text-[#0A1128] mb-1">{data.leadDoctor.name}</p>
<p className="text-sm text-slate-600 mb-3">{data.leadDoctor.credentials}</p>
<div className="flex items-center gap-4">
<div className="flex items-center gap-1">
{Array.from({ length: 5 }).map((_, i) => (
<Star
key={i}
size={16}
className={i < Math.round(data.leadDoctor.rating) ? 'text-[#6B2D8B] fill-[#6B2D8B]' : 'text-slate-200'}
/>
))}
<span className="text-sm font-semibold text-[#0A1128] ml-1">
{data.leadDoctor.rating}
</span>
</div>
<span className="text-sm text-slate-500">
{formatNumber(data.leadDoctor.reviewCount)}
</span>
</div>
</motion.div>
{/* Certifications */}
{data.certifications.length > 0 && (
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.4 }}
>
<p className="text-sm font-semibold text-slate-700 mb-3"> </p>
<div className="flex flex-wrap gap-2">
{data.certifications.map((cert) => (
<span
key={cert}
className="rounded-full bg-white/60 backdrop-blur-sm border border-white/40 px-3 py-1 text-sm font-medium text-slate-700"
>
{cert}
</span>
))}
</div>
</motion.div>
)}
</SectionWrapper>
);
}