Add three new frontend page sections: - Industrial parks (khu-cong-nghiep): listing, detail, filter bar - Transfer listings (chuyen-nhuong): search, category tabs, detail - AI reports dashboard: list, create, viewer with TOC Includes components, API clients, hooks, server helpers, i18n keys, navigation links in public and dashboard layouts, and lint fixes. Co-Authored-By: Paperclip <noreply@paperclip.ing>
106 lines
4.2 KiB
TypeScript
106 lines
4.2 KiB
TypeScript
'use client';
|
|
|
|
import { Building2, MapPin } from 'lucide-react';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Card, CardContent } from '@/components/ui/card';
|
|
import { Link } from '@/i18n/navigation';
|
|
import { type IndustrialParkListItem, PARK_STATUS_COLORS, PARK_STATUS_LABELS, REGION_LABELS } from '@/lib/khu-cong-nghiep-api';
|
|
|
|
interface ParkCardProps {
|
|
park: IndustrialParkListItem;
|
|
}
|
|
|
|
export function ParkCard({ park }: ParkCardProps) {
|
|
const occupancyColor =
|
|
park.occupancyRate >= 90 ? 'text-red-600' :
|
|
park.occupancyRate >= 70 ? 'text-amber-600' :
|
|
'text-green-600';
|
|
|
|
return (
|
|
<Link href={`/khu-cong-nghiep/${park.slug}`}>
|
|
<Card className="group h-full transition-shadow hover:shadow-lg">
|
|
<CardContent className="p-5">
|
|
{/* Header */}
|
|
<div className="mb-3 flex items-start justify-between gap-2">
|
|
<div className="min-w-0 flex-1">
|
|
<h3 className="line-clamp-1 font-semibold text-foreground group-hover:text-primary">
|
|
{park.name}
|
|
</h3>
|
|
{park.nameEn && (
|
|
<p className="line-clamp-1 text-xs text-muted-foreground">{park.nameEn}</p>
|
|
)}
|
|
</div>
|
|
<Badge className={PARK_STATUS_COLORS[park.status]} variant="secondary">
|
|
{PARK_STATUS_LABELS[park.status]}
|
|
</Badge>
|
|
</div>
|
|
|
|
{/* 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" />
|
|
<span className="line-clamp-1">{park.province} · {REGION_LABELS[park.region]}</span>
|
|
</div>
|
|
|
|
{/* Stats grid */}
|
|
<div className="mb-3 grid grid-cols-2 gap-3">
|
|
<div className="rounded-md bg-muted p-2">
|
|
<div className="text-xs text-muted-foreground">Diện tích</div>
|
|
<div className="font-semibold">{park.totalAreaHa.toLocaleString()} ha</div>
|
|
</div>
|
|
<div className="rounded-md bg-muted p-2">
|
|
<div className="text-xs text-muted-foreground">Lấp đầy</div>
|
|
<div className={`font-semibold ${occupancyColor}`}>{park.occupancyRate}%</div>
|
|
</div>
|
|
<div className="rounded-md bg-muted p-2">
|
|
<div className="text-xs text-muted-foreground">Còn trống</div>
|
|
<div className="font-semibold">{park.remainingAreaHa.toLocaleString()} ha</div>
|
|
</div>
|
|
<div className="rounded-md bg-muted p-2">
|
|
<div className="text-xs text-muted-foreground">Doanh nghiệp</div>
|
|
<div className="font-semibold">{park.tenantCount}</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Rent info */}
|
|
{park.landRentUsdM2Year && (
|
|
<div className="mb-3 flex items-center gap-4 text-sm">
|
|
<div>
|
|
<span className="text-muted-foreground">Thuê đất: </span>
|
|
<span className="font-medium text-primary">${park.landRentUsdM2Year}/m²/năm</span>
|
|
</div>
|
|
{park.rbfRentUsdM2Month && (
|
|
<div>
|
|
<span className="text-muted-foreground">NX: </span>
|
|
<span className="font-medium">${park.rbfRentUsdM2Month}/m²/th</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Industries */}
|
|
<div className="flex flex-wrap gap-1">
|
|
{park.targetIndustries.slice(0, 3).map((industry) => (
|
|
<Badge key={industry} variant="outline" className="text-xs">
|
|
{industry}
|
|
</Badge>
|
|
))}
|
|
{park.targetIndustries.length > 3 && (
|
|
<Badge variant="outline" className="text-xs">
|
|
+{park.targetIndustries.length - 3}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
|
|
{/* Footer */}
|
|
<div className="mt-3 flex items-center gap-3 border-t pt-3 text-xs text-muted-foreground">
|
|
<div className="flex items-center gap-1">
|
|
<Building2 className="h-3 w-3" />
|
|
{park.developer}
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</Link>
|
|
);
|
|
}
|