- دیتابیس/مدل/اسکیما: land_area (مساحت عرصه)، pilot_floors (طبقات روی همکف/پیلوت) و سه ستون واحدها (مسکونی/تجاری/اداری) بهجای «واحدها»ی تکی - فایل استاتیک projectsData (دادههای ماک) حذف شد؛ لیست، جزئیات و «پروژههای شاخص» همه از دیتابیس میخوانند - برچسبهای i18n فهرست مشخصات مطابق شیت تیم
97 lines
4.1 KiB
TypeScript
97 lines
4.1 KiB
TypeScript
import { Link } from 'react-router-dom'
|
|
import { useStatusLabel } from '../api'
|
|
import { useLang } from '../i18n/lang'
|
|
import { PremiumButton } from './PremiumButton'
|
|
export type StaticProject = {
|
|
id: string
|
|
slug: string
|
|
name: { en: string; fa: string }
|
|
location: { en: string; fa: string }
|
|
area: { en: string; fa: string }
|
|
status: 'done' | 'progress'
|
|
}
|
|
import type { ProjectCardItem } from '../data/site'
|
|
|
|
type ProjectCardProps = {
|
|
project: StaticProject | ProjectCardItem;
|
|
imageUrl?: string;
|
|
detailHref?: string;
|
|
}
|
|
|
|
export function ProjectCard({ project, imageUrl, detailHref }: ProjectCardProps) {
|
|
const isOngoing = project.status === 'progress'
|
|
const statusText = useStatusLabel(project.status)
|
|
const { t, lang } = useLang()
|
|
|
|
// Type guards for backward compatibility
|
|
const isStatic = 'name' in project;
|
|
|
|
// Resolve localized texts
|
|
const title = isStatic
|
|
? (project.name[lang as 'en' | 'fa'] || project.name.en)
|
|
: (project as ProjectCardItem).title;
|
|
|
|
const locRaw = isStatic ? project.location : (project as ProjectCardItem).location;
|
|
const location = typeof locRaw === 'string' ? locRaw : (locRaw?.[lang as 'en' | 'fa'] || locRaw?.en);
|
|
|
|
const areaRaw = isStatic ? project.area : (project as ProjectCardItem).area;
|
|
const area = typeof areaRaw === 'string' ? areaRaw : (areaRaw?.[lang as 'en' | 'fa'] || areaRaw?.en);
|
|
|
|
const finalImageUrl = imageUrl || (project as ProjectCardItem).image;
|
|
const finalDetailHref = detailHref || (project as ProjectCardItem).detailHref || '#';
|
|
|
|
const dotColor = isOngoing ? 'bg-copper animate-pulse' : 'bg-ivory/60'
|
|
const textColor = isOngoing ? 'text-copper' : 'text-ivory/90'
|
|
|
|
return (
|
|
<article className="group flex h-full flex-col overflow-hidden rounded-2xl border border-white/10 bg-char/85 backdrop-blur-xl shadow-[0_10px_30px_rgba(0,0,0,0.3)] transition-all duration-500 ease-lux hover:-translate-y-2 hover:border-copper/50 hover:shadow-[0_20px_40px_rgba(0,0,0,0.6),0_0_30px_rgba(197,143,92,0.2)] focus:outline-none focus-visible:ring-2 focus-visible:ring-copper">
|
|
<Link to={finalDetailHref} className="flex flex-1 flex-col focus:outline-none">
|
|
{/* Fixed height image wrapper */}
|
|
<div className="relative w-full overflow-hidden">
|
|
{finalImageUrl && (
|
|
<img
|
|
src={finalImageUrl}
|
|
alt={title}
|
|
className="w-full h-[250px] object-cover transition-transform duration-700 ease-lux group-hover:scale-110"
|
|
/>
|
|
)}
|
|
|
|
{/* نشانگر وضعیت کپسولی شیشهای */}
|
|
<div
|
|
className={`absolute top-4 ltr:left-4 rtl:right-4 flex items-center gap-2 rounded-full border border-white/15 bg-ink/75 px-4 py-1.5 text-xs font-light backdrop-blur-xl shadow-[inset_0_1px_1px_rgba(255,255,255,0.2),0_4px_15px_rgba(0,0,0,0.4)] ${textColor}`}
|
|
>
|
|
<span className={`h-2 w-2 rounded-full ${dotColor} ${isOngoing ? 'shadow-[0_0_10px_rgba(197,143,92,0.9)]' : ''}`} />
|
|
{statusText}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex flex-1 flex-col p-7 pb-4">
|
|
<div className="text-center">
|
|
<h3 className="font-pinar text-xl font-bold text-ivory transition-colors duration-300 group-hover:text-copper">
|
|
{title}
|
|
</h3>
|
|
{location && <p className="mt-4 text-sm font-light text-ivory/60">{location}</p>}
|
|
</div>
|
|
|
|
{area && (
|
|
<>
|
|
<div className="my-6 h-px w-full bg-white/10" />
|
|
<div className="mt-auto flex items-center justify-end text-sm font-light text-ivory/60">
|
|
{/* واحد زیربنا در محتوای انگلیسی داخل خود مقدار است — فقط در فارسی اضافه میشود */}
|
|
<span>{lang === 'fa' ? `${area} ${t('sqm')}` : area}</span>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</Link>
|
|
|
|
{/* پایهٔ کارت: یک دکمه واحد */}
|
|
<div className="flex flex-col gap-2 px-7 pb-7">
|
|
<PremiumButton to={finalDetailHref} className="w-full">
|
|
{t('viewProject')}
|
|
</PremiumButton>
|
|
</div>
|
|
</article>
|
|
)
|
|
}
|