fix(web): add proper Vietnamese diacritics to all dashboard and listing pages
Vietnamese text throughout the frontend was missing accent marks (diacritics), using plain ASCII instead of proper Unicode characters. Fixed all user-visible text across dashboard, analytics, listings, search, and chart components. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -15,12 +15,12 @@ import {
|
||||
|
||||
const DistrictBarChart = dynamic(
|
||||
() => import('@/components/charts/district-bar-chart').then((mod) => mod.DistrictBarChart),
|
||||
{ ssr: false, loading: () => <div className="flex h-64 items-center justify-center text-muted-foreground">Dang tai bieu do...</div> },
|
||||
{ ssr: false, loading: () => <div className="flex h-64 items-center justify-center text-muted-foreground">Đang tải biểu đồ...</div> },
|
||||
);
|
||||
|
||||
const PriceTrendChart = dynamic(
|
||||
() => import('@/components/charts/price-trend-chart').then((mod) => mod.PriceTrendChart),
|
||||
{ ssr: false, loading: () => <div className="flex h-64 items-center justify-center text-muted-foreground">Dang tai bieu do...</div> },
|
||||
{ ssr: false, loading: () => <div className="flex h-64 items-center justify-center text-muted-foreground">Đang tải biểu đồ...</div> },
|
||||
);
|
||||
|
||||
const CITIES = ['Ho Chi Minh', 'Ha Noi', 'Da Nang'];
|
||||
@@ -29,14 +29,14 @@ 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)} ty`;
|
||||
if (num >= 1_000_000) return `${(num / 1_000_000).toFixed(0)} trieu`;
|
||||
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/m2`;
|
||||
return `${price.toLocaleString('vi-VN')} d/m2`;
|
||||
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 }) {
|
||||
@@ -91,7 +91,7 @@ export default function AnalyticsPage() {
|
||||
setTrendDistrict(firstDistrict);
|
||||
}
|
||||
})
|
||||
.catch(() => setError('Khong the tai du lieu phan tich'))
|
||||
.catch(() => setError('Không thể tải dữ liệu phân tích'))
|
||||
.finally(() => setLoading(false));
|
||||
}, [city, period]);
|
||||
|
||||
@@ -131,16 +131,16 @@ export default function AnalyticsPage() {
|
||||
const trendChartData = priceTrend.map((p) => ({
|
||||
period: p.period,
|
||||
'Gia/m2': Math.round(p.avgPriceM2 / 1_000_000),
|
||||
'Tin dang': p.totalListings,
|
||||
'Tin đăng': p.totalListings,
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">Phan tich thi truong</h1>
|
||||
<h1 className="text-3xl font-bold">Phân tích thị trường</h1>
|
||||
<p className="mt-2 text-muted-foreground">
|
||||
Bao cao thi truong bat dong san - {period}
|
||||
Báo cáo thị trường bất động sản - {period}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
@@ -163,7 +163,7 @@ export default function AnalyticsPage() {
|
||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardDescription>Tong tin dang</CardDescription>
|
||||
<CardDescription>Tổng tin đăng</CardDescription>
|
||||
<CardTitle className="text-2xl">
|
||||
{loading ? '...' : totalListings.toLocaleString('vi-VN')}
|
||||
</CardTitle>
|
||||
@@ -171,7 +171,7 @@ export default function AnalyticsPage() {
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardDescription>Gia TB/m2</CardDescription>
|
||||
<CardDescription>Giá TB/m²</CardDescription>
|
||||
<CardTitle className="text-2xl">
|
||||
{loading ? '...' : formatPriceM2(avgPriceM2)}
|
||||
</CardTitle>
|
||||
@@ -179,15 +179,15 @@ export default function AnalyticsPage() {
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardDescription>Ngay trung binh de ban</CardDescription>
|
||||
<CardDescription>Ngày trung bình để bán</CardDescription>
|
||||
<CardTitle className="text-2xl">
|
||||
{loading ? '...' : `${avgDaysOnMarket.toFixed(0)} ngay`}
|
||||
{loading ? '...' : `${avgDaysOnMarket.toFixed(0)} ngày`}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardDescription>So quan/huyen</CardDescription>
|
||||
<CardDescription>Số quận/huyện</CardDescription>
|
||||
<CardTitle className="text-2xl">
|
||||
{loading ? '...' : new Set(marketReport.map((d) => d.district)).size}
|
||||
</CardTitle>
|
||||
@@ -198,9 +198,9 @@ export default function AnalyticsPage() {
|
||||
{/* Tabs */}
|
||||
<Tabs value={tab} onValueChange={setTab}>
|
||||
<TabsList>
|
||||
<TabsTrigger value="overview">Tong quan</TabsTrigger>
|
||||
<TabsTrigger value="trends">Xu huong gia</TabsTrigger>
|
||||
<TabsTrigger value="districts">Chi tiet quan</TabsTrigger>
|
||||
<TabsTrigger value="overview">Tổng quan</TabsTrigger>
|
||||
<TabsTrigger value="trends">Xu hướng giá</TabsTrigger>
|
||||
<TabsTrigger value="districts">Chi tiết quận</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* Overview Tab */}
|
||||
@@ -209,17 +209,17 @@ export default function AnalyticsPage() {
|
||||
{/* Bar Chart - Price by District */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">Gia trung binh theo quan</CardTitle>
|
||||
<CardDescription>Trieu VND/m2 tai {city}</CardDescription>
|
||||
<CardTitle className="text-lg">Giá trung bình theo quận</CardTitle>
|
||||
<CardDescription>Triệu VND/m² tại {city}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{loading ? (
|
||||
<div className="flex h-64 items-center justify-center text-muted-foreground">
|
||||
Dang tai...
|
||||
Đang tải...
|
||||
</div>
|
||||
) : barChartData.length === 0 ? (
|
||||
<div className="flex h-64 items-center justify-center text-muted-foreground">
|
||||
Chua co du lieu
|
||||
Chưa có dữ liệu
|
||||
</div>
|
||||
) : (
|
||||
<DistrictBarChart data={barChartData} height={300} dataKey="price" />
|
||||
@@ -230,17 +230,17 @@ export default function AnalyticsPage() {
|
||||
{/* Heatmap - Card Grid */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">Ban do gia theo quan</CardTitle>
|
||||
<CardDescription>So sanh gia trung binh/m2 tai {city}</CardDescription>
|
||||
<CardTitle className="text-lg">Bản đồ giá theo quận</CardTitle>
|
||||
<CardDescription>So sánh giá trung bình/m² tại {city}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{loading ? (
|
||||
<div className="flex h-64 items-center justify-center text-muted-foreground">
|
||||
Dang tai...
|
||||
Đang tải...
|
||||
</div>
|
||||
) : heatmap.length === 0 ? (
|
||||
<div className="flex h-64 items-center justify-center text-muted-foreground">
|
||||
Chua co du lieu
|
||||
Chưa có dữ liệu
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-2 sm:grid-cols-2">
|
||||
@@ -267,7 +267,7 @@ export default function AnalyticsPage() {
|
||||
{formatPriceM2(point.avgPriceM2)}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{point.totalListings} tin dang
|
||||
{point.totalListings} tin đăng
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -299,20 +299,20 @@ export default function AnalyticsPage() {
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">
|
||||
Xu huong gia - {trendDistrict || 'Chon quan'}
|
||||
Xu hướng giá - {trendDistrict || 'Chọn quận'}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Bien dong gia trung binh/m2 qua cac quy (Can ho)
|
||||
Biến động giá trung bình/m² qua các quý (Căn hộ)
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{trendLoading ? (
|
||||
<div className="flex h-64 items-center justify-center text-muted-foreground">
|
||||
Dang tai...
|
||||
Đang tải...
|
||||
</div>
|
||||
) : trendChartData.length === 0 ? (
|
||||
<div className="flex h-64 items-center justify-center text-muted-foreground">
|
||||
Chua co du lieu xu huong
|
||||
Chưa có dữ liệu xu hướng
|
||||
</div>
|
||||
) : (
|
||||
<PriceTrendChart data={trendChartData} height={350} />
|
||||
@@ -328,31 +328,31 @@ export default function AnalyticsPage() {
|
||||
{/* Stats Table */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">Thong ke chi tiet theo quan</CardTitle>
|
||||
<CardTitle className="text-lg">Thống kê chi tiết theo quận</CardTitle>
|
||||
<CardDescription>
|
||||
Du lieu thi truong bat dong san tai {city} - {period}
|
||||
Dữ liệu thị trường bất động sản tại {city} - {period}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{loading ? (
|
||||
<div className="flex h-48 items-center justify-center text-muted-foreground">
|
||||
Dang tai...
|
||||
Đang tải...
|
||||
</div>
|
||||
) : districtStats.length === 0 ? (
|
||||
<div className="flex h-48 items-center justify-center text-muted-foreground">
|
||||
Chua co du lieu
|
||||
Chưa có dữ liệu
|
||||
</div>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b text-left">
|
||||
<th className="pb-2 pr-4 font-medium">Quan</th>
|
||||
<th className="pb-2 pr-4 font-medium">Loai BDS</th>
|
||||
<th className="pb-2 pr-4 font-medium text-right">Gia trung vi</th>
|
||||
<th className="pb-2 pr-4 font-medium text-right">Gia/m2</th>
|
||||
<th className="pb-2 pr-4 font-medium text-right">Tin dang</th>
|
||||
<th className="pb-2 pr-4 font-medium text-right">Ngay ban</th>
|
||||
<th className="pb-2 pr-4 font-medium">Quận</th>
|
||||
<th className="pb-2 pr-4 font-medium">Loại BĐS</th>
|
||||
<th className="pb-2 pr-4 font-medium text-right">Giá trung vị</th>
|
||||
<th className="pb-2 pr-4 font-medium text-right">Giá/m²</th>
|
||||
<th className="pb-2 pr-4 font-medium text-right">Tin đăng</th>
|
||||
<th className="pb-2 pr-4 font-medium text-right">Ngày bán</th>
|
||||
<th className="pb-2 font-medium text-right">YoY</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -391,17 +391,17 @@ export default function AnalyticsPage() {
|
||||
{/* Market Report Cards */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">Bao cao thi truong</CardTitle>
|
||||
<CardDescription>Tong hop chi so thi truong theo tung quan</CardDescription>
|
||||
<CardTitle className="text-lg">Báo cáo thị trường</CardTitle>
|
||||
<CardDescription>Tổng hợp chỉ số thị trường theo từng quận</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{loading ? (
|
||||
<div className="flex h-48 items-center justify-center text-muted-foreground">
|
||||
Dang tai...
|
||||
Đang tải...
|
||||
</div>
|
||||
) : marketReport.length === 0 ? (
|
||||
<div className="flex h-48 items-center justify-center text-muted-foreground">
|
||||
Chua co du lieu
|
||||
Chưa có dữ liệu
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
@@ -411,25 +411,25 @@ export default function AnalyticsPage() {
|
||||
<h3 className="font-semibold">{district.district}</h3>
|
||||
<div className="mt-2 space-y-1 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Gia trung vi</span>
|
||||
<span className="text-muted-foreground">Giá trung vị</span>
|
||||
<span className="font-medium">
|
||||
{formatPrice(district.medianPrice)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Gia/m2</span>
|
||||
<span className="text-muted-foreground">Giá/m²</span>
|
||||
<span>{formatPriceM2(district.avgPriceM2)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Tin dang</span>
|
||||
<span className="text-muted-foreground">Tin đăng</span>
|
||||
<span>{district.totalListings}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Ton kho</span>
|
||||
<span className="text-muted-foreground">Tồn kho</span>
|
||||
<span>{district.inventoryLevel}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Thay doi YoY</span>
|
||||
<span className="text-muted-foreground">Thay đổi YoY</span>
|
||||
<YoYBadge value={district.yoyChange} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user