'use client'; import { useEffect, useState } from 'react'; import Link from 'next/link'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { ListingStatusBadge } from '@/components/listings/listing-status-badge'; import { analyticsApi, type MarketReportDistrict, type HeatmapDataPoint, } from '@/lib/analytics-api'; import { listingsApi, type ListingDetail, type PaginatedResult } from '@/lib/listings-api'; import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, } from 'recharts'; const CITY = 'Ho Chi Minh'; const PERIOD = '2026-Q1'; function formatPrice(priceStr: string): string { const num = Number(priceStr); if (num >= 1_000_000_000) return `${(num / 1_000_000_000).toFixed(1)} ty`; if (num >= 1_000_000) return `${(num / 1_000_000).toFixed(0)} trieu`; return num.toLocaleString('vi-VN'); } function formatPriceM2(price: number): string { if (price >= 1_000_000) return `${(price / 1_000_000).toFixed(1)} tr/m2`; return `${price.toLocaleString('vi-VN')} d/m2`; } interface StatCardProps { title: string; value: string; description?: string; trend?: number | null; } function StatCard({ title, value, description, trend }: StatCardProps) { return ( {title} {value} {(description || trend != null) && (
{trend != null && ( = 0 ? 'text-green-600' : 'text-red-600'}`} > {trend >= 0 ? '+' : ''} {trend.toFixed(1)}% )} {description && ( {description} )}
)}
); } export default function DashboardPage() { const [marketReport, setMarketReport] = useState([]); const [heatmap, setHeatmap] = useState([]); const [listings, setListings] = useState | null>(null); const [loading, setLoading] = useState(true); useEffect(() => { setLoading(true); Promise.all([ analyticsApi.getMarketReport(CITY, PERIOD).catch(() => ({ districts: [] as MarketReportDistrict[] })), analyticsApi.getHeatmap(CITY, PERIOD).catch(() => ({ dataPoints: [] as HeatmapDataPoint[] })), listingsApi.search({ page: 1, limit: 6 }).catch(() => null), ]) .then(([report, heatmapData, listingsResult]) => { setMarketReport(report.districts); setHeatmap(heatmapData.dataPoints); setListings(listingsResult); }) .finally(() => setLoading(false)); }, []); const totalListings = marketReport.reduce((sum, d) => sum + d.totalListings, 0); const avgPriceM2 = marketReport.length > 0 ? marketReport.reduce((sum, d) => sum + d.avgPriceM2, 0) / marketReport.length : 0; const avgDaysOnMarket = marketReport.length > 0 ? marketReport.reduce((sum, d) => sum + d.daysOnMarket, 0) / marketReport.length : 0; const avgYoy = marketReport.filter((d) => d.yoyChange != null).length > 0 ? marketReport .filter((d) => d.yoyChange != null) .reduce((sum, d) => sum + (d.yoyChange ?? 0), 0) / marketReport.filter((d) => d.yoyChange != null).length : null; const myListingsCount = listings?.total ?? 0; const totalViews = listings?.data.reduce((s, l) => s + l.viewCount, 0) ?? 0; const totalInquiries = listings?.data.reduce((s, l) => s + l.inquiryCount, 0) ?? 0; const chartData = heatmap .sort((a, b) => b.avgPriceM2 - a.avgPriceM2) .slice(0, 8) .map((p) => ({ district: p.district.replace(/^Quan\s*/i, 'Q.').replace(/^Huyen\s*/i, 'H.'), 'Gia/m2': Math.round(p.avgPriceM2 / 1_000_000), listings: p.totalListings, })); return (

Bang dieu khien

Tong quan thi truong va tin dang cua ban

{/* Stats overview */}
{/* Market overview + quick stats */}
{/* Price chart */} Gia trung binh theo quan {CITY} - {PERIOD} (trieu VND/m2) {loading ? (
Dang tai...
) : chartData.length === 0 ? (
Chua co du lieu
) : ( [`${value} tr/m2`, 'Gia']} /> )}
{/* Market summary */} Thi truong {CITY} Chi so chinh - {PERIOD}
Tong tin dang {loading ? '...' : totalListings.toLocaleString('vi-VN')}
Gia TB/m2 {loading ? '...' : formatPriceM2(avgPriceM2)}
Ngay TB de ban {loading ? '...' : `${avgDaysOnMarket.toFixed(0)} ngay`}
So quan {loading ? '...' : new Set(marketReport.map((d) => d.district)).size}
{/* Recent listings */}
Tin dang gan day Danh sach tin dang moi nhat cua ban
{loading ? (
Dang tai...
) : !listings || listings.data.length === 0 ? (

Chua co tin dang nao

) : (
{listings.data.slice(0, 5).map((listing) => (
{listing.property.media.length > 0 ? ( ) : (
N/A
)}

{listing.property.title}

{listing.property.district}, {listing.property.city}

{formatPrice(listing.priceVND)}

{listing.viewCount} luot xem {listing.inquiryCount} lien he
))}
)}
); }