o2o-clinicad-frontend/src/features/report/ui/facebook/FacebookPageCard.tsx

207 lines
8.7 KiB
TypeScript

import ExternalLinkIcon from "@/assets/icons/external-link.svg?react";
import EyeIcon from "@/assets/icons/eye.svg?react";
import TrendingUpIcon from "@/assets/icons/trending-up.svg?react";
import AlertTriangleIcon from "@/assets/report/alert-triangle.svg?react";
import CheckCircleIcon from "@/assets/report/check-circle.svg?react";
import FacebookMarkIcon from "@/assets/report/facebook-mark.svg?react";
import ImageIcon from "@/assets/report/image.svg?react";
import Link2Icon from "@/assets/report/link-2.svg?react";
import MessageCircleIcon from "@/assets/report/message-circle.svg?react";
import type { FacebookPage } from "@/features/report/types/facebookAudit";
import { facebookLangBadgeClass } from "@/features/report/ui/facebook/langBadgeClass";
import { formatCompactNumber } from "@/lib/formatNumber";
import { safeUrl } from "@/lib/safeUrl";
export type FacebookPageCardProps = {
page: FacebookPage;
index: number;
};
export function FacebookPageCard({ page, index }: FacebookPageCardProps) {
const isKR = page.language === "KR";
const isLogoMismatch = page.logo.includes("불일치");
const isLowFollowers = page.followers < 500;
const lowEngagement = page.engagement?.includes("0~3") ?? false;
const domainMismatch = page.linkedDomain?.includes("다름") ?? false;
return (
<article
className={`rounded-2xl border p-6 card-shadow animate-fade-in-up ${
isKR && isLowFollowers
? "bg-[var(--color-status-critical-bg)]/30 border-[var(--color-status-critical-border)]/60"
: "bg-white border-neutral-20"
}`}
style={{ animationDelay: `${index * 100}ms` }}
>
<div className="flex items-center justify-between gap-2 mb-4 flex-wrap">
<div className="flex items-center gap-2 flex-wrap min-w-0">
<span
className={`label-12 font-medium px-3 py-1 rounded-full break-keep ${facebookLangBadgeClass(page.language)}`}
>
{page.label}
</span>
<div className="w-8 h-8 rounded-lg bg-[#1877F2] flex items-center justify-center text-white shrink-0 [&_svg]:block">
<FacebookMarkIcon width={16} height={16} aria-hidden />
</div>
</div>
<div className="flex flex-wrap gap-2 justify-end">
{isKR && isLowFollowers ? (
<span className="label-12 font-medium px-3 py-1 rounded-full bg-[var(--color-status-critical-bg)] text-[var(--color-status-critical-text)] break-keep">
</span>
) : null}
{page.hasWhatsApp ? (
<span className="label-12 font-medium px-3 py-1 rounded-full bg-[var(--color-status-good-bg)] text-[var(--color-status-good-text)] break-keep">
WhatsApp
</span>
) : null}
</div>
</div>
<h3 className="title-18 text-navy-900 mb-1 break-keep">{page.pageName}</h3>
<p className="label-12 text-neutral-60 mb-4 break-keep">{page.category}</p>
<div className="grid grid-cols-3 gap-2 mb-5">
<div className="rounded-xl bg-neutral-10 p-3 text-center">
<p className="label-12 text-neutral-60 break-keep"></p>
<p
className={`title-18 ${
isLowFollowers ? "text-[var(--color-status-critical-text)]" : "text-navy-900"
}`}
>
{formatCompactNumber(page.followers)}
</p>
</div>
<div className="rounded-xl bg-neutral-10 p-3 text-center">
<p className="label-12 text-neutral-60 break-keep"></p>
<p
className={`title-18 ${
page.reviews === 0 ? "text-[var(--color-status-critical-text)]" : "text-navy-900"
}`}
>
{page.reviews}
</p>
</div>
<div className="rounded-xl bg-neutral-10 p-3 text-center">
<p className="label-12 text-neutral-60 break-keep"></p>
<p className="title-18 text-navy-900">{formatCompactNumber(page.following)}</p>
</div>
</div>
<div className="space-y-3 mb-5 body-14">
<div className="flex items-center justify-between gap-2 py-1 border-b border-neutral-20 last:border-0">
<span className="text-neutral-60 flex items-center gap-2 shrink-0 break-keep">
<EyeIcon width={13} height={13} className="shrink-0" aria-hidden />
</span>
<span className="body-14-medium text-navy-900 text-right break-keep">{page.recentPostAge}</span>
</div>
{page.postFrequency ? (
<div className="flex items-center justify-between gap-2 py-1 border-b border-neutral-20 last:border-0">
<span className="text-neutral-60 flex items-center gap-2 shrink-0 break-keep">
<TrendingUpIcon width={13} height={13} className="shrink-0" aria-hidden />
</span>
<span className="body-14-medium text-navy-900 text-right break-keep min-w-0">{page.postFrequency}</span>
</div>
) : null}
{page.topContentType ? (
<div className="flex items-center justify-between gap-2 py-1 border-b border-neutral-20 last:border-0">
<span className="text-neutral-60 flex items-center gap-2 shrink-0 break-keep">
<ImageIcon width={13} height={13} className="shrink-0" aria-hidden />
</span>
<span className="body-14-medium text-navy-900 text-right text-xs max-w-[180px] min-w-0 break-keep">
{page.topContentType}
</span>
</div>
) : null}
{page.engagement ? (
<div className="flex items-center justify-between gap-2 py-1 border-b border-neutral-20 last:border-0">
<span className="text-neutral-60 flex items-center gap-2 shrink-0 break-keep">
<MessageCircleIcon width={13} height={13} className="shrink-0" aria-hidden />
</span>
<span
className={`body-14-medium text-right text-xs max-w-[180px] min-w-0 break-keep ${
lowEngagement ? "text-[var(--color-status-critical-text)]" : "text-navy-900"
}`}
>
{page.engagement}
</span>
</div>
) : null}
</div>
<div
className={`rounded-xl p-4 mb-4 ${
isLogoMismatch
? "bg-[var(--color-status-critical-bg)] border border-[var(--color-status-critical-border)]"
: "bg-[var(--color-status-good-bg)] border border-[var(--color-status-good-border)]"
}`}
>
<div className="flex items-start gap-2 mb-2">
{isLogoMismatch ? (
<AlertTriangleIcon
width={16}
height={16}
className="text-[var(--color-status-critical-text)] shrink-0 mt-0.5"
aria-hidden
/>
) : (
<CheckCircleIcon
width={16}
height={16}
className="text-[var(--color-status-good-dot)] shrink-0 mt-0.5"
aria-hidden
/>
)}
<p
className={`body-14-medium break-keep ${
isLogoMismatch ? "text-[var(--color-status-critical-text)]" : "text-[var(--color-status-good-text)]"
}`}
>
{page.logo}
</p>
</div>
<p
className={`label-12 leading-relaxed ml-6 break-keep ${
isLogoMismatch ? "text-[var(--color-status-critical-text)]" : "text-[var(--color-status-good-text)]"
}`}
>
{page.logoDescription}
</p>
</div>
<div className="rounded-xl bg-neutral-10 p-3 mb-4">
<div className="flex items-center gap-2 mb-1">
<Link2Icon width={12} height={12} className="text-neutral-60 shrink-0" aria-hidden />
<p className="label-12 text-neutral-60 uppercase tracking-wide break-keep"> </p>
</div>
<p
className={`body-14 font-mono break-keep ${
domainMismatch ? "text-[var(--color-status-warning-text)]" : "text-neutral-70"
}`}
>
{page.linkedDomain || page.link}
</p>
</div>
<div className="rounded-xl bg-neutral-10 p-3 mb-4">
<p className="label-12 text-neutral-60 uppercase tracking-wide mb-1 break-keep">Bio</p>
<p className="body-14 text-neutral-70 italic break-keep">&quot;{page.bio}&quot;</p>
</div>
<a
href={safeUrl(page.url)}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 label-12 text-violet-700 hover:underline break-all cursor-pointer"
>
<ExternalLinkIcon aria-hidden />
<span className="break-keep">{page.url}</span>
</a>
</article>
);
}