},
);
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 (
-
Phan tich thi truong
+
Phân tích thị trường
- Bao cao thi truong bat dong san - {period}
+ Báo cáo thị trường bất động sản - {period}
@@ -163,7 +163,7 @@ export default function AnalyticsPage() {
- Tong tin dang
+ Tổng tin đăng
{loading ? '...' : totalListings.toLocaleString('vi-VN')}
@@ -171,7 +171,7 @@ export default function AnalyticsPage() {
- Gia TB/m2
+ Giá TB/m²
{loading ? '...' : formatPriceM2(avgPriceM2)}
@@ -179,15 +179,15 @@ export default function AnalyticsPage() {
- Ngay trung binh de ban
+ Ngày trung bình để bán
- {loading ? '...' : `${avgDaysOnMarket.toFixed(0)} ngay`}
+ {loading ? '...' : `${avgDaysOnMarket.toFixed(0)} ngày`}
- So quan/huyen
+ Số quận/huyện
{loading ? '...' : new Set(marketReport.map((d) => d.district)).size}
@@ -198,9 +198,9 @@ export default function AnalyticsPage() {
{/* Tabs */}
- Tong quan
- Xu huong gia
- Chi tiet quan
+ Tổng quan
+ Xu hướng giá
+ Chi tiết quận
{/* Overview Tab */}
@@ -209,17 +209,17 @@ export default function AnalyticsPage() {
{/* Bar Chart - Price by District */}
- Gia trung binh theo quan
- Trieu VND/m2 tai {city}
+ Giá trung bình theo quận
+ Triệu VND/m² tại {city}
{loading ? (
- Dang tai...
+ Đang tải...
) : barChartData.length === 0 ? (
- Chua co du lieu
+ Chưa có dữ liệu
) : (
@@ -230,17 +230,17 @@ export default function AnalyticsPage() {
{/* Heatmap - Card Grid */}
- Ban do gia theo quan
- So sanh gia trung binh/m2 tai {city}
+ Bản đồ giá theo quận
+ So sánh giá trung bình/m² tại {city}
{loading ? (
- Dang tai...
+ Đang tải...
) : heatmap.length === 0 ? (
- Chua co du lieu
+ Chưa có dữ liệu
) : (
@@ -267,7 +267,7 @@ export default function AnalyticsPage() {
{formatPriceM2(point.avgPriceM2)}
- {point.totalListings} tin dang
+ {point.totalListings} tin đăng
);
@@ -299,20 +299,20 @@ export default function AnalyticsPage() {
- Xu huong gia - {trendDistrict || 'Chon quan'}
+ Xu hướng giá - {trendDistrict || 'Chọn quận'}
- 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ộ)
{trendLoading ? (
- Dang tai...
+ Đang tải...
) : trendChartData.length === 0 ? (
- Chua co du lieu xu huong
+ Chưa có dữ liệu xu hướng
) : (
@@ -328,31 +328,31 @@ export default function AnalyticsPage() {
{/* Stats Table */}
- Thong ke chi tiet theo quan
+ Thống kê chi tiết theo quận
- 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}
{loading ? (
- Dang tai...
+ Đang tải...
) : districtStats.length === 0 ? (
- Chua co du lieu
+ Chưa có dữ liệu
) : (
-
Quan
-
Loai BDS
-
Gia trung vi
-
Gia/m2
-
Tin dang
-
Ngay ban
+
Quận
+
Loại BĐS
+
Giá trung vị
+
Giá/m²
+
Tin đăng
+
Ngày bán
YoY
@@ -391,17 +391,17 @@ export default function AnalyticsPage() {
{/* Market Report Cards */}
- Bao cao thi truong
- Tong hop chi so thi truong theo tung quan
+ Báo cáo thị trường
+ Tổng hợp chỉ số thị trường theo từng quận
{loading ? (
- Dang tai...
+ Đang tải...
) : marketReport.length === 0 ? (
- Chua co du lieu
+ Chưa có dữ liệu
) : (
@@ -411,25 +411,25 @@ export default function AnalyticsPage() {
{district.district}
- Gia trung vi
+ Giá trung vị
{formatPrice(district.medianPrice)}
- Ngay TB de ban
+ Ngày TB để bán
- {loading ? '...' : `${avgDaysOnMarket.toFixed(0)} ngay`}
+ {loading ? '...' : `${avgDaysOnMarket.toFixed(0)} ngày`}
- So quan
+ Số quận
{loading ? '...' : new Set(marketReport.map((d) => d.district)).size}
@@ -221,7 +221,7 @@ export default function DashboardPage() {
@@ -233,26 +233,26 @@ export default function DashboardPage() {
- Tin dang gan day
- Danh sach tin dang moi nhat cua ban
+ Tin đăng gần đây
+ Danh sách tin đăng mới nhất của bạn
{loading ? (
- Dang tai...
+ Đang tải...
) : !listings || listings.data.length === 0 ? (
-
Chua co tin dang nao
+
Chưa có tin đăng nào
@@ -292,8 +292,8 @@ export default function DashboardPage() {
- {listing.viewCount} luot xem
- {listing.inquiryCount} lien he
+ {listing.viewCount} lượt xem
+ {listing.inquiryCount} liên hệ
))}
diff --git a/apps/web/app/(dashboard)/listings/page.tsx b/apps/web/app/(dashboard)/listings/page.tsx
index 4a46433..9f66c1b 100644
--- a/apps/web/app/(dashboard)/listings/page.tsx
+++ b/apps/web/app/(dashboard)/listings/page.tsx
@@ -16,8 +16,8 @@ import {
import { PROPERTY_TYPES, TRANSACTION_TYPES, LISTING_STATUSES } from '@/lib/validations/listings';
function formatPrice(priceVND: string): string {
const num = Number(priceVND);
- 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');
}
@@ -77,13 +77,13 @@ export default function ListingsPage() {
{/* Header */}
-
Quan ly tin dang
+
Quản lý tin đăng
- Quan ly, theo doi va cap nhat cac tin dang cua ban
+ Quản lý, theo dõi và cập nhật các tin đăng của bạn
-
+
@@ -91,13 +91,13 @@ export default function ListingsPage() {
- Tong tin dang
+ Tổng tin đăng{loading ? '...' : stats.total}
- Dang hoat dong
+ Đang hoạt động
{loading ? '...' : stats.active}
@@ -105,7 +105,7 @@ export default function ListingsPage() {
- Cho duyet
+ Chờ duyệt
{loading ? '...' : stats.pending}
@@ -113,7 +113,7 @@ export default function ListingsPage() {
- Tong luot xem
+ Tổng lượt xem{loading ? '...' : stats.views}
@@ -128,7 +128,7 @@ export default function ListingsPage() {
}
className="w-40"
>
-
+
{TRANSACTION_TYPES.map((t) => (
+
{PROPERTY_TYPES.map((t) => (
+
{Object.entries(LISTING_STATUSES).map(([key, { label }]) => (
@@ -187,10 +187,10 @@ export default function ListingsPage() {
) : !result || result.data.length === 0 ? (
-
Chua co tin dang nao
+
Chưa có tin đăng nào
@@ -228,7 +228,7 @@ export default function ListingsPage() {
- {listing.viewCount} luot xem
- {listing.inquiryCount} lien he
- {listing.saveCount} da luu
+ {listing.viewCount} lượt xem
+ {listing.inquiryCount} liên hệ
+ {listing.saveCount} đã lưu
@@ -259,14 +259,14 @@ export default function ListingsPage() {
-
Tin dang
-
Loai
-
Gia
-
Dien tich
-
Trang thai
-
Luot xem
-
Lien he
-
Ngay dang
+
Tin đăng
+
Loại
+
Giá
+
Diện tích
+
Trạng thái
+
Lượt xem
+
Liên hệ
+
Ngày đăng
@@ -311,7 +311,7 @@ export default function ListingsPage() {