feat(web): add industrial compare page, listing search, and Mapbox park map
- Add interactive Mapbox map to /khu-cong-nghiep landing page with park markers and popups - Build compare page at /khu-cong-nghiep/so-sanh with recharts RadarChart and detailed comparison table - Build listing search page at /khu-cong-nghiep/cho-thue with filters for property type, lease type, area, and price - Add IndustrialListing types, API client functions, and React Query hooks Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
95
apps/web/components/khu-cong-nghiep/listing-card.tsx
Normal file
95
apps/web/components/khu-cong-nghiep/listing-card.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
'use client';
|
||||
|
||||
import { Calendar, Eye, MapPin, Ruler } from 'lucide-react';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import {
|
||||
type IndustrialListingItem,
|
||||
LEASE_TYPE_LABELS,
|
||||
PROPERTY_TYPE_LABELS,
|
||||
} from '@/lib/khu-cong-nghiep-api';
|
||||
|
||||
interface ListingCardProps {
|
||||
listing: IndustrialListingItem;
|
||||
}
|
||||
|
||||
export function IndustrialListingCard({ listing }: ListingCardProps) {
|
||||
const priceText = listing.priceUsdM2
|
||||
? `$${listing.priceUsdM2}/${listing.pricingUnit ?? 'm²/tháng'}`
|
||||
: listing.totalLeasePrice
|
||||
? `$${listing.totalLeasePrice.toLocaleString()}`
|
||||
: 'Liên hệ';
|
||||
|
||||
const leaseTermText =
|
||||
listing.minLeaseYears && listing.maxLeaseYears
|
||||
? `${listing.minLeaseYears}–${listing.maxLeaseYears} năm`
|
||||
: listing.minLeaseYears
|
||||
? `Từ ${listing.minLeaseYears} năm`
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Card className="group h-full transition-shadow hover:shadow-lg">
|
||||
<CardContent className="p-5">
|
||||
{/* Header badges */}
|
||||
<div className="mb-3 flex flex-wrap items-center gap-2">
|
||||
<Badge variant="secondary" className="bg-blue-100 text-blue-800">
|
||||
{PROPERTY_TYPE_LABELS[listing.propertyType]}
|
||||
</Badge>
|
||||
<Badge variant="outline">
|
||||
{LEASE_TYPE_LABELS[listing.leaseType]}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h3 className="mb-2 line-clamp-2 font-semibold text-foreground group-hover:text-primary">
|
||||
{listing.title}
|
||||
</h3>
|
||||
|
||||
{/* Park location */}
|
||||
<div className="mb-3 flex items-center gap-1 text-sm text-muted-foreground">
|
||||
<MapPin className="h-3.5 w-3.5 shrink-0" />
|
||||
<a
|
||||
href={`/khu-cong-nghiep/${listing.parkSlug}`}
|
||||
className="line-clamp-1 hover:text-primary hover:underline"
|
||||
>
|
||||
{listing.parkName}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Stats grid */}
|
||||
<div className="mb-3 grid grid-cols-2 gap-3">
|
||||
<div className="rounded-md bg-muted p-2">
|
||||
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
||||
<Ruler className="h-3 w-3" />
|
||||
Diện tích
|
||||
</div>
|
||||
<div className="font-semibold">{listing.areaM2.toLocaleString()} m²</div>
|
||||
</div>
|
||||
<div className="rounded-md bg-muted p-2">
|
||||
<div className="text-xs text-muted-foreground">Giá thuê</div>
|
||||
<div className="font-semibold text-primary">{priceText}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Additional info */}
|
||||
<div className="flex flex-wrap items-center gap-3 text-xs text-muted-foreground">
|
||||
{listing.ceilingHeightM && (
|
||||
<span>Cao trần: {listing.ceilingHeightM}m</span>
|
||||
)}
|
||||
{leaseTermText && (
|
||||
<span className="flex items-center gap-1">
|
||||
<Calendar className="h-3 w-3" />
|
||||
{leaseTermText}
|
||||
</span>
|
||||
)}
|
||||
{listing.viewCount > 0 && (
|
||||
<span className="flex items-center gap-1">
|
||||
<Eye className="h-3 w-3" />
|
||||
{listing.viewCount}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user