Files
goodgo-platform/apps/web/components/search/property-card.tsx
Ho Ngoc Hai 2502aa69b7 fix: production readiness — resolve build, lint, and code quality issues
- Fix Next.js build failure: remove duplicate route at (dashboard)/listings/[id]
  that conflicted with (public)/listings/[id] (same URL path in two route groups)
- Fix 772 ESLint errors: auto-fix import ordering (import-x/order), remove unused
  imports/variables, convert empty interfaces to type aliases, replace require()
  with ESM imports, fix consistent-type-imports violations
- Add CLAUDE.md for developer onboarding documentation
- All checks pass: 0 lint errors, typecheck clean, 230 tests passing, build success

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-08 07:15:06 +07:00

119 lines
4.8 KiB
TypeScript

'use client';
import Image from 'next/image';
import Link from 'next/link';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent } from '@/components/ui/card';
import type { ListingDetail } from '@/lib/listings-api';
function formatPrice(priceVND: string): string {
const num = Number(priceVND);
if (num >= 1_000_000_000) return `${(num / 1_000_000_000).toFixed(1)} tỷ`;
if (num >= 1_000_000) return `${(num / 1_000_000).toFixed(0)} triệu`;
return num.toLocaleString('vi-VN');
}
const PROPERTY_TYPE_LABELS: Record<string, string> = {
APARTMENT: 'Căn hộ',
HOUSE: 'Nhà riêng',
VILLA: 'Biệt thự',
LAND: 'Đất nền',
OFFICE: 'Văn phòng',
SHOPHOUSE: 'Shophouse',
};
interface PropertyCardProps {
listing: ListingDetail;
compact?: boolean;
}
export function PropertyCard({ listing, compact }: PropertyCardProps) {
const transactionLabel = listing.transactionType === 'SALE' ? 'Bán' : 'Cho thuê';
const propertyTypeLabel = PROPERTY_TYPE_LABELS[listing.property.propertyType] || listing.property.propertyType;
return (
<article
aria-label={`${listing.property.title}${transactionLabel} ${propertyTypeLabel}, ${formatPrice(listing.priceVND)} VNĐ`}
>
<Link href={`/listings/${listing.id}`}>
<Card className="group h-full overflow-hidden transition-shadow hover:shadow-md">
<div className={`relative bg-muted ${compact ? 'aspect-[16/10]' : 'aspect-[4/3]'}`}>
{listing.property.media.length > 0 ? (
<Image
src={listing.property.media[0]?.url ?? ''}
alt={`Ảnh bất động sản: ${listing.property.title}`}
fill
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
className="object-cover transition-transform group-hover:scale-105"
/>
) : (
<div className="flex h-full items-center justify-center text-muted-foreground" aria-hidden="true">
Chưa nh
</div>
)}
<div className="absolute left-2 top-2 flex gap-1">
<Badge variant="default" className="text-xs">
{transactionLabel}
</Badge>
<Badge variant="secondary" className="text-xs">
{propertyTypeLabel}
</Badge>
</div>
{listing.property.media.length > 1 && (
<div className="absolute bottom-2 right-2">
<Badge variant="outline" className="bg-black/50 text-xs text-white border-none" aria-label={`${listing.property.media.length} ảnh`}>
{listing.property.media.length} nh
</Badge>
</div>
)}
</div>
<CardContent className="p-4">
<p className="text-lg font-bold text-primary">
{formatPrice(listing.priceVND)} VNĐ
{listing.transactionType === 'RENT' && listing.rentPriceMonthly && (
<span className="text-sm font-normal text-muted-foreground">/tháng</span>
)}
</p>
<h3 className="mt-1 line-clamp-1 font-medium">{listing.property.title}</h3>
<p className="mt-1 line-clamp-1 text-sm text-muted-foreground">
{listing.property.address}, {listing.property.district}, {listing.property.city}
</p>
<ul className="mt-3 flex flex-wrap gap-1.5" aria-label="Thông tin bất động sản">
<li>
<Badge variant="secondary" className="text-xs">
{listing.property.areaM2} m²
</Badge>
</li>
{listing.property.bedrooms != null && (
<li>
<Badge variant="secondary" className="text-xs">
{listing.property.bedrooms} PN
</Badge>
</li>
)}
{listing.property.bathrooms != null && listing.property.bathrooms > 0 && (
<li>
<Badge variant="secondary" className="text-xs">
{listing.property.bathrooms} PT
</Badge>
</li>
)}
{listing.property.direction && (
<li>
<Badge variant="outline" className="text-xs">
Hướng {listing.property.direction === 'NORTH' ? 'Bắc' :
listing.property.direction === 'SOUTH' ? 'Nam' :
listing.property.direction === 'EAST' ? 'Đông' :
listing.property.direction === 'WEST' ? 'Tây' :
listing.property.direction}
</Badge>
</li>
)}
</ul>
</CardContent>
</Card>
</Link>
</article>
);
}