'use client';
import dynamic from 'next/dynamic';
import { useEffect, useState } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
import {
useMarketReport,
useHeatmap,
useDistrictStats,
usePriceTrend,
} from '@/lib/hooks/use-analytics';
const DistrictBarChart = dynamic(
() => import('@/components/charts/district-bar-chart').then((mod) => mod.DistrictBarChart),
{ ssr: false, loading: () =>
Đang tải biểu đồ...
},
);
const PriceTrendChart = dynamic(
() => import('@/components/charts/price-trend-chart').then((mod) => mod.PriceTrendChart),
{ ssr: false, loading: () => Đang tải biểu đồ...
},
);
const DistrictHeatmap = dynamic(
() => import('@/components/charts/district-heatmap').then((mod) => mod.DistrictHeatmap),
{ ssr: false, loading: () => Đang tải bản đồ nhiệt...
},
);
const AgentPerformance = dynamic(
() => import('@/components/charts/agent-performance').then((mod) => mod.AgentPerformance),
{ ssr: false, loading: () => Đang tải...
},
);
const CITIES = ['Ho Chi Minh', 'Ha Noi', 'Da Nang'];
const CURRENT_PERIOD = '2026-Q1';
const TREND_PERIODS = ['2025-Q1', '2025-Q2', '2025-Q3', '2025-Q4', '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)} tỷ`;
if (num >= 1_000_000) return `${(num / 1_000_000).toFixed(0)} triệu`;
return num.toLocaleString('vi-VN');
}
function formatPriceM2(price: number): string {
if (price >= 1_000_000) return `${(price / 1_000_000).toFixed(1)} tr/m²`;
return `${price.toLocaleString('vi-VN')} đ/m²`;
}
function YoYBadge({ value }: { value: number | null }) {
if (value === null) return N/A ;
const isPositive = value >= 0;
return (
{isPositive ? '+' : ''}
{value.toFixed(1)}%
);
}
export default function AnalyticsPage() {
const [city, setCity] = useState(CITIES[0] ?? 'Ho Chi Minh');
const period = CURRENT_PERIOD;
const [tab, setTab] = useState('overview');
const [trendDistrict, setTrendDistrict] = useState('');
const { data: reportData, isLoading: reportLoading, error: reportError } = useMarketReport(city, period);
const { data: heatmapData, isLoading: heatmapLoading } = useHeatmap(city, period);
const { data: statsData, isLoading: statsLoading } = useDistrictStats(city, period);
const { data: trendData, isLoading: trendLoading } = usePriceTrend(
trendDistrict,
city,
'APARTMENT',
TREND_PERIODS,
);
const loading = reportLoading || heatmapLoading || statsLoading;
const error = reportError ? 'Không thể tải dữ liệu phân tích' : null;
const marketReport = reportData?.districts ?? [];
const heatmap = heatmapData?.dataPoints ?? [];
const districtStats = statsData?.districts ?? [];
const priceTrend = trendData?.trend ?? [];
// Auto-select first district for trend
const firstDistrict = marketReport[0]?.district ?? '';
useEffect(() => {
if (firstDistrict && !trendDistrict) {
setTrendDistrict(firstDistrict);
}
}, [firstDistrict, trendDistrict]);
const totalListings = marketReport.reduce((sum, d) => sum + d.totalListings, 0);
const avgDaysOnMarket =
marketReport.length > 0
? marketReport.reduce((sum, d) => sum + d.daysOnMarket, 0) / marketReport.length
: 0;
const avgPriceM2 =
marketReport.length > 0
? marketReport.reduce((sum, d) => sum + d.avgPriceM2, 0) / marketReport.length
: 0;
const uniqueDistricts = [...new Set(marketReport.map((d) => d.district))];
// Chart data for bar chart
const barChartData = heatmap
.sort((a, b) => b.avgPriceM2 - a.avgPriceM2)
.map((p) => ({
district: p.district.replace(/^Quan\s*/i, 'Q.').replace(/^Huyen\s*/i, 'H.'),
price: Math.round(p.avgPriceM2 / 1_000_000),
listings: p.totalListings,
}));
// Chart data for line chart
const trendChartData = priceTrend.map((p) => ({
period: p.period,
'Gia/m2': Math.round(p.avgPriceM2 / 1_000_000),
'Tin đăng': p.totalListings,
}));
return (
Phân tích thị trường
Báo cáo thị trường bất động sản - {period}
{CITIES.map((c) => (
setCity(c)}
>
{c}
))}
{error &&
{error}
}
{/* Summary Cards */}
Tổng tin đăng
{loading ? '...' : totalListings.toLocaleString('vi-VN')}
Giá TB/m²
{loading ? '...' : formatPriceM2(avgPriceM2)}
Ngày trung bình để bán
{loading ? '...' : `${avgDaysOnMarket.toFixed(0)} ngày`}
Số quận/huyện
{loading ? '...' : new Set(marketReport.map((d) => d.district)).size}
{/* Tabs */}
Tổng quan
Xu hướng giá
Chi tiết quận
Hiệu suất
{/* Overview Tab */}
{/* Bar Chart - Price by District */}
Giá trung bình theo quận
Triệu VND/m² tại {city}
{loading ? (
Đang tải...
) : barChartData.length === 0 ? (
Chưa có dữ liệu
) : (
)}
{/* Heatmap - Mapbox Map */}
Bản đồ nhiệt giá theo quận
So sánh giá trung bình/m² tại {city}
{loading ? (
Đang tải...
) : heatmap.length === 0 ? (
Chưa có dữ liệu
) : (
{
setTrendDistrict(district);
setTab('trends');
}}
/>
)}
{/* Trends Tab */}
{/* District selector */}
{uniqueDistricts.map((d) => (
setTrendDistrict(d)}
>
{d}
))}
Xu hướng giá - {trendDistrict || 'Chọn quận'}
Biến động giá trung bình/m² qua các quý (Căn hộ)
{trendLoading ? (
Đang tải...
) : trendChartData.length === 0 ? (
Chưa có dữ liệu xu hướng
) : (
)}
{/* District Stats Tab */}
{/* Stats Table */}
Thống kê chi tiết theo quận
Dữ liệu thị trường bất động sản tại {city} - {period}
{loading ? (
Đang tải...
) : districtStats.length === 0 ? (
Chưa có dữ liệu
) : (
Quận
Loại BĐS
Giá trung vị
Giá/m²
Tin đăng
Ngày bán
YoY
{districtStats.map((stat, i) => (
{stat.district}
{stat.propertyType}
{formatPrice(stat.medianPrice)}
{formatPriceM2(stat.avgPriceM2)}
{stat.totalListings}
{stat.daysOnMarket.toFixed(0)}
))}
)}
{/* Market Report Cards */}
Báo cáo thị trường
Tổng hợp chỉ số thị trường theo từng quận
{loading ? (
Đang tải...
) : marketReport.length === 0 ? (
Chưa có dữ liệu
) : (
{[...new Map(marketReport.map((d) => [d.district, d])).values()].map(
(district) => (
{district.district}
Giá trung vị
{formatPrice(district.medianPrice)}
Giá/m²
{formatPriceM2(district.avgPriceM2)}
Tin đăng
{district.totalListings}
Tồn kho
{district.inventoryLevel}
Thay đổi YoY
),
)}
)}
{/* Agent Performance Tab */}
);
}