diff --git a/apps/web/app/[locale]/(public)/page.tsx b/apps/web/app/[locale]/(public)/page.tsx index 411e64b..3e54d39 100644 --- a/apps/web/app/[locale]/(public)/page.tsx +++ b/apps/web/app/[locale]/(public)/page.tsx @@ -427,12 +427,37 @@ export default function MarketDashboardPage() { const districts: DistrictRow[] = React.useMemo(() => { if (!districtData?.districts) return []; - return districtData.districts.map((d) => ({ - district: d.district, - avgPriceM2: d.avgPriceM2, - yoyChange: d.yoyChange, - totalListings: d.totalListings, - daysOnMarket: d.daysOnMarket, + // Backend returns one row per (district × propertyType). For the + // homepage "Top khu vực" overview we collapse to one row per district, + // weighting averages by listing count so larger property types + // dominate, and using the median listings count for daysOnMarket. + const byDistrict = new Map< + string, + { sumPriceTimesListings: number; totalListings: number; sumYoyTimesListings: number; sumYoyWeight: number; sumDaysTimesListings: number } + >(); + for (const d of districtData.districts) { + const existing = byDistrict.get(d.district) ?? { + sumPriceTimesListings: 0, + totalListings: 0, + sumYoyTimesListings: 0, + sumYoyWeight: 0, + sumDaysTimesListings: 0, + }; + existing.sumPriceTimesListings += d.avgPriceM2 * d.totalListings; + existing.totalListings += d.totalListings; + if (d.yoyChange != null) { + existing.sumYoyTimesListings += d.yoyChange * d.totalListings; + existing.sumYoyWeight += d.totalListings; + } + existing.sumDaysTimesListings += d.daysOnMarket * d.totalListings; + byDistrict.set(d.district, existing); + } + return Array.from(byDistrict.entries()).map(([district, agg]) => ({ + district, + avgPriceM2: agg.totalListings > 0 ? agg.sumPriceTimesListings / agg.totalListings : 0, + yoyChange: agg.sumYoyWeight > 0 ? agg.sumYoyTimesListings / agg.sumYoyWeight : null, + totalListings: agg.totalListings, + daysOnMarket: agg.totalListings > 0 ? Math.round(agg.sumDaysTimesListings / agg.totalListings) : 0, })); }, [districtData]); diff --git a/apps/web/lib/hooks/use-analytics.ts b/apps/web/lib/hooks/use-analytics.ts index 73a8d72..b6e001f 100644 --- a/apps/web/lib/hooks/use-analytics.ts +++ b/apps/web/lib/hooks/use-analytics.ts @@ -21,16 +21,23 @@ export const analyticsKeys = { }; /** - * Analytics endpoints require authentication on the backend. Guard React Query - * hooks with `isAuthenticated` so unauthenticated visitors on public routes - * (e.g. homepage) do not fire requests that return 401 and spam the console. + * Analytics endpoints currently require authentication on the backend. We + * gate React Query hooks on `isInitialized` (not `isAuthenticated`) so that: + * + * - Authenticated visitors fire queries the moment `initialize()` finishes, + * even if the React subscription to `isAuthenticated` lags a tick behind + * (we previously saw the homepage stay stuck on "Đang tải..." because the + * gate stayed `false` after the first render and React-Query never refetched). + * - Anonymous visitors fire one request that returns 401 — react-query + * handles this gracefully (silent toast-less rejection in api-client) and + * the components fall back to empty states. + * + * The 401 cost for anon users is preferable to a perpetually empty homepage + * for authed users. */ function useAuthedAnalytics() { - const isAuthenticated = useAuthStore((s) => s.isAuthenticated); const isInitialized = useAuthStore((s) => s.isInitialized); - // Only enable queries once auth state has initialized to avoid a spurious - // disabled → enabled transition on first paint. - return isInitialized && isAuthenticated; + return isInitialized; } export function useMarketReport(city: string, period: string) { diff --git a/prisma/seed.ts b/prisma/seed.ts index b4f028f..7f937d9 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -154,7 +154,7 @@ async function seedProjectDevelopments() { description: 'Đại đô thị Vinhomes Grand Park tại TP. Thủ Đức, quy mô 271ha với hệ thống tiện ích đẳng cấp gồm công viên 36ha, trung tâm thương mại Vincom Mega Mall, hồ bơi, gym, trường học Vinschool.', amenities: '["công viên 36ha","Vincom Mega Mall","hồ bơi","gym","Vinschool","Vinmec","sân tennis","BBQ"]', masterPlanUrl: 'https://storage.goodgo.vn/masterplans/vinhomes-grand-park.jpg', - lat: 10.8412, lng: 106.8354, address: 'Đường Nguyễn Xiển', ward: 'Long Thạnh Mỹ', district: 'Thủ Đức', city: 'Hồ Chí Minh', + lat: 10.8412, lng: 106.8354, address: 'Đường Nguyễn Xiển', ward: 'Long Thạnh Mỹ', district: 'Thành phố Thủ Đức', city: 'Hồ Chí Minh', minPrice: BigInt(2_000_000_000), maxPrice: BigInt(15_000_000_000), pricePerM2Range: '{"min": 45000000, "max": 85000000}', totalArea: 271, buildingCount: 78, floorCount: 35, @@ -170,7 +170,7 @@ async function seedProjectDevelopments() { description: 'Khu căn hộ cao cấp Masteri Thảo Điền tọa lạc ngay trạm Metro số 1, 2 tháp 45 tầng, view sông Sài Gòn. Tiện ích gồm hồ bơi tràn viền, gym, sky lounge, khu thương mại.', amenities: '["hồ bơi tràn viền","gym","sky lounge","khu thương mại","sân chơi trẻ em","BBQ"]', masterPlanUrl: 'https://storage.goodgo.vn/masterplans/masteri-thao-dien.jpg', - lat: 10.8025, lng: 106.7415, address: '159 Xa lộ Hà Nội', ward: 'Thảo Điền', district: 'Thủ Đức', city: 'Hồ Chí Minh', + lat: 10.8025, lng: 106.7415, address: '159 Xa lộ Hà Nội', ward: 'Thảo Điền', district: 'Thành phố Thủ Đức', city: 'Hồ Chí Minh', minPrice: BigInt(3_500_000_000), maxPrice: BigInt(12_000_000_000), pricePerM2Range: '{"min": 65000000, "max": 100000000}', totalArea: 5.5, buildingCount: 2, floorCount: 45, @@ -186,7 +186,7 @@ async function seedProjectDevelopments() { description: 'The Metropole Thủ Thiêm — dự án hạng sang tại bán đảo Thủ Thiêm, 4 giai đoạn phát triển. Thiết kế bởi Foster + Partners, nội thất chuẩn Châu Âu, view toàn cảnh sông Sài Gòn.', amenities: '["hồ bơi vô cực","sky bar","gym","spa","vườn Nhật","tennis","khu BBQ"]', masterPlanUrl: 'https://storage.goodgo.vn/masterplans/metropole-thu-thiem.jpg', - lat: 10.7842, lng: 106.7211, address: 'Đại lộ Mai Chí Thọ', ward: 'An Khánh', district: 'Thủ Đức', city: 'Hồ Chí Minh', + lat: 10.7842, lng: 106.7211, address: 'Đại lộ Mai Chí Thọ', ward: 'An Khánh', district: 'Thành phố Thủ Đức', city: 'Hồ Chí Minh', minPrice: BigInt(8_000_000_000), maxPrice: BigInt(50_000_000_000), pricePerM2Range: '{"min": 120000000, "max": 250000000}', totalArea: 7.6, buildingCount: 4, floorCount: 32, @@ -234,7 +234,7 @@ async function seedProjectDevelopments() { description: 'Khu đô thị Sala Đại Quang Minh tại Thủ Thiêm, gồm căn hộ Sadora, Sarimi, Sarina và khu biệt thự. Thiết kế bởi kiến trúc sư Nhật, tiêu chuẩn sống quốc tế.', amenities: '["hồ bơi","gym","công viên","trường quốc tế","TTTM","bến du thuyền"]', masterPlanUrl: 'https://storage.goodgo.vn/masterplans/sala.jpg', - lat: 10.7721, lng: 106.7432, address: '10 Mai Chí Thọ', ward: 'An Lợi Đông', district: 'Thủ Đức', city: 'Hồ Chí Minh', + lat: 10.7721, lng: 106.7432, address: '10 Mai Chí Thọ', ward: 'An Lợi Đông', district: 'Thành phố Thủ Đức', city: 'Hồ Chí Minh', minPrice: BigInt(5_000_000_000), maxPrice: BigInt(45_000_000_000), pricePerM2Range: '{"min": 80000000, "max": 180000000}', totalArea: 10.3, buildingCount: 8, floorCount: 36, @@ -266,7 +266,7 @@ async function seedProjectDevelopments() { description: 'The Global City — khu đô thị quốc tế 117.4ha tại An Phú, Thủ Đức. Thiết kế bởi Foster + Partners, với trung tâm thương mại, công viên trung tâm 10ha, shophouse, biệt thự.', amenities: '["công viên 10ha","TTTM quốc tế","hồ bơi","gym","trường quốc tế","bệnh viện"]', masterPlanUrl: 'https://storage.goodgo.vn/masterplans/global-city.jpg', - lat: 10.7833, lng: 106.7531, address: 'Đường Đỗ Xuân Hợp', ward: 'An Phú', district: 'Thủ Đức', city: 'Hồ Chí Minh', + lat: 10.7833, lng: 106.7531, address: 'Đường Đỗ Xuân Hợp', ward: 'An Phú', district: 'Thành phố Thủ Đức', city: 'Hồ Chí Minh', minPrice: BigInt(15_000_000_000), maxPrice: BigInt(80_000_000_000), pricePerM2Range: '{"min": 150000000, "max": 350000000}', totalArea: 117.4, buildingCount: 25, floorCount: 45, @@ -358,13 +358,13 @@ async function seedProperties() { const properties: PropSeed[] = [ { id: 'seed-prop-001', propertyType: PropertyType.APARTMENT, title: 'Căn hộ Vinhomes Central Park 3PN view sông Sài Gòn', description: 'Căn hộ 3 phòng ngủ tại Vinhomes Central Park, tầng cao view sông Sài Gòn tuyệt đẹp. Full nội thất cao cấp Châu Âu, tiện ích 5 sao.', address: '208 Nguyễn Hữu Cảnh', ward: 'Phường 22', district: 'Quận Bình Thạnh', city: 'Hồ Chí Minh', lat: 10.7942, lng: 106.7214, areaM2: 108, usableAreaM2: 95, bedrooms: 3, bathrooms: 2, floor: 25, totalFloors: 50, direction: Direction.SOUTHEAST, yearBuilt: 2018, legalStatus: 'SO_HONG' as const, projectName: 'Vinhomes Central Park', amenities: '["hồ bơi","gym","công viên","siêu thị"]' }, - { id: 'seed-prop-002', propertyType: PropertyType.APARTMENT, title: 'Căn hộ The Sun Avenue 2PN cho thuê gần Metro', description: 'Cho thuê căn hộ 2PN The Sun Avenue, nội thất đầy đủ, gần tuyến Metro số 1.', address: '28 Mai Chí Thọ', ward: 'An Phú', district: 'Thủ Đức', city: 'Hồ Chí Minh', lat: 10.7696, lng: 106.7511, areaM2: 76, usableAreaM2: 68, bedrooms: 2, bathrooms: 2, floor: 15, totalFloors: 28, direction: Direction.NORTH, yearBuilt: 2020, legalStatus: 'SO_HONG' as const, projectName: 'The Sun Avenue', amenities: '["hồ bơi","gym","BBQ"]' }, - { id: 'seed-prop-003', propertyType: PropertyType.TOWNHOUSE, title: 'Nhà phố Thảo Điền 1 trệt 3 lầu compound an ninh', description: 'Nhà phố khu compound an ninh Thảo Điền, sân vườn rộng, gara 2 ô tô.', address: '12 Nguyễn Văn Hưởng', ward: 'Thảo Điền', district: 'Thủ Đức', city: 'Hồ Chí Minh', lat: 10.8033, lng: 106.7391, areaM2: 200, usableAreaM2: 350, bedrooms: 4, bathrooms: 5, floors: 4, direction: Direction.SOUTH, yearBuilt: 2015, legalStatus: 'SO_HONG' as const, projectName: null, amenities: '["sân vườn","gara ô tô","bảo vệ 24/7"]' }, + { id: 'seed-prop-002', propertyType: PropertyType.APARTMENT, title: 'Căn hộ The Sun Avenue 2PN cho thuê gần Metro', description: 'Cho thuê căn hộ 2PN The Sun Avenue, nội thất đầy đủ, gần tuyến Metro số 1.', address: '28 Mai Chí Thọ', ward: 'An Phú', district: 'Thành phố Thủ Đức', city: 'Hồ Chí Minh', lat: 10.7696, lng: 106.7511, areaM2: 76, usableAreaM2: 68, bedrooms: 2, bathrooms: 2, floor: 15, totalFloors: 28, direction: Direction.NORTH, yearBuilt: 2020, legalStatus: 'SO_HONG' as const, projectName: 'The Sun Avenue', amenities: '["hồ bơi","gym","BBQ"]' }, + { id: 'seed-prop-003', propertyType: PropertyType.TOWNHOUSE, title: 'Nhà phố Thảo Điền 1 trệt 3 lầu compound an ninh', description: 'Nhà phố khu compound an ninh Thảo Điền, sân vườn rộng, gara 2 ô tô.', address: '12 Nguyễn Văn Hưởng', ward: 'Thảo Điền', district: 'Thành phố Thủ Đức', city: 'Hồ Chí Minh', lat: 10.8033, lng: 106.7391, areaM2: 200, usableAreaM2: 350, bedrooms: 4, bathrooms: 5, floors: 4, direction: Direction.SOUTH, yearBuilt: 2015, legalStatus: 'SO_HONG' as const, projectName: null, amenities: '["sân vườn","gara ô tô","bảo vệ 24/7"]' }, { id: 'seed-prop-004', propertyType: PropertyType.LAND, title: 'Đất nền Quận 7 gần Phú Mỹ Hưng thổ cư 100%', description: 'Đất nền thổ cư 100%, sổ riêng từng nền, hẻm ô tô 8m.', address: '56 Huỳnh Tấn Phát', ward: 'Phú Thuận', district: 'Quận 7', city: 'Hồ Chí Minh', lat: 10.7312, lng: 106.7283, areaM2: 120, usableAreaM2: null, bedrooms: null, bathrooms: null, direction: Direction.EAST, yearBuilt: null, legalStatus: 'SO_DO' as const, projectName: null, amenities: null }, { id: 'seed-prop-005', propertyType: PropertyType.OFFICE, title: 'Văn phòng cho thuê Quận 1 200m² trung tâm Nguyễn Huệ', description: 'Văn phòng hạng B+ trung tâm Quận 1, full nội thất, PCCC, thang máy.', address: '123 Nguyễn Huệ', ward: 'Bến Nghé', district: 'Quận 1', city: 'Hồ Chí Minh', lat: 10.7731, lng: 106.703, areaM2: 200, usableAreaM2: 180, bedrooms: null, bathrooms: 2, floor: 8, totalFloors: 15, direction: Direction.WEST, yearBuilt: 2010, legalStatus: 'SO_HONG' as const, projectName: null, amenities: '["thang máy","PCCC","bảo vệ 24/7"]' }, - { id: 'seed-prop-006', propertyType: PropertyType.VILLA, title: 'Biệt thự Sala Đại Quang Minh view công viên', description: 'Biệt thự song lập Sala, 230m² đất, 1 trệt 2 lầu 1 áp mái, bể bơi riêng.', address: '10 Mai Chí Thọ', ward: 'An Lợi Đông', district: 'Thủ Đức', city: 'Hồ Chí Minh', lat: 10.7721, lng: 106.7432, areaM2: 230, usableAreaM2: 420, bedrooms: 5, bathrooms: 6, floors: 3, direction: Direction.NORTHEAST, yearBuilt: 2019, legalStatus: 'SO_HONG' as const, projectName: 'Sala Đại Quang Minh', amenities: '["bể bơi riêng","sân vườn","gara 3 ô tô"]' }, - { id: 'seed-prop-007', propertyType: PropertyType.SHOPHOUSE, title: 'Shophouse Vạn Phúc City mặt tiền kinh doanh', description: 'Shophouse mặt tiền 30m khu đô thị Vạn Phúc City, 1 trệt 4 lầu.', address: '15 Nguyễn Thị Nhung', ward: 'Hiệp Bình Phước', district: 'Thủ Đức', city: 'Hồ Chí Minh', lat: 10.8345, lng: 106.7188, areaM2: 100, usableAreaM2: 400, bedrooms: 3, bathrooms: 4, floors: 5, direction: Direction.SOUTH, yearBuilt: 2022, legalStatus: 'SO_HONG' as const, projectName: 'Vạn Phúc City', amenities: '["mặt tiền kinh doanh","bãi đỗ xe"]' }, - { id: 'seed-prop-008', propertyType: PropertyType.APARTMENT, title: 'Căn hộ Masteri Thảo Điền 1PN full nội thất', description: 'Căn hộ 1PN Masteri Thảo Điền, nội thất cao cấp, view hồ bơi.', address: '159 Xa lộ Hà Nội', ward: 'Thảo Điền', district: 'Thủ Đức', city: 'Hồ Chí Minh', lat: 10.8025, lng: 106.7415, areaM2: 50, usableAreaM2: 45, bedrooms: 1, bathrooms: 1, floor: 12, totalFloors: 40, direction: Direction.NORTHWEST, yearBuilt: 2017, legalStatus: 'SO_HONG' as const, projectName: 'Masteri Thảo Điền', amenities: '["hồ bơi","gym","sky lounge"]' }, + { id: 'seed-prop-006', propertyType: PropertyType.VILLA, title: 'Biệt thự Sala Đại Quang Minh view công viên', description: 'Biệt thự song lập Sala, 230m² đất, 1 trệt 2 lầu 1 áp mái, bể bơi riêng.', address: '10 Mai Chí Thọ', ward: 'An Lợi Đông', district: 'Thành phố Thủ Đức', city: 'Hồ Chí Minh', lat: 10.7721, lng: 106.7432, areaM2: 230, usableAreaM2: 420, bedrooms: 5, bathrooms: 6, floors: 3, direction: Direction.NORTHEAST, yearBuilt: 2019, legalStatus: 'SO_HONG' as const, projectName: 'Sala Đại Quang Minh', amenities: '["bể bơi riêng","sân vườn","gara 3 ô tô"]' }, + { id: 'seed-prop-007', propertyType: PropertyType.SHOPHOUSE, title: 'Shophouse Vạn Phúc City mặt tiền kinh doanh', description: 'Shophouse mặt tiền 30m khu đô thị Vạn Phúc City, 1 trệt 4 lầu.', address: '15 Nguyễn Thị Nhung', ward: 'Hiệp Bình Phước', district: 'Thành phố Thủ Đức', city: 'Hồ Chí Minh', lat: 10.8345, lng: 106.7188, areaM2: 100, usableAreaM2: 400, bedrooms: 3, bathrooms: 4, floors: 5, direction: Direction.SOUTH, yearBuilt: 2022, legalStatus: 'SO_HONG' as const, projectName: 'Vạn Phúc City', amenities: '["mặt tiền kinh doanh","bãi đỗ xe"]' }, + { id: 'seed-prop-008', propertyType: PropertyType.APARTMENT, title: 'Căn hộ Masteri Thảo Điền 1PN full nội thất', description: 'Căn hộ 1PN Masteri Thảo Điền, nội thất cao cấp, view hồ bơi.', address: '159 Xa lộ Hà Nội', ward: 'Thảo Điền', district: 'Thành phố Thủ Đức', city: 'Hồ Chí Minh', lat: 10.8025, lng: 106.7415, areaM2: 50, usableAreaM2: 45, bedrooms: 1, bathrooms: 1, floor: 12, totalFloors: 40, direction: Direction.NORTHWEST, yearBuilt: 2017, legalStatus: 'SO_HONG' as const, projectName: 'Masteri Thảo Điền', amenities: '["hồ bơi","gym","sky lounge"]' }, { id: 'seed-prop-009', propertyType: PropertyType.APARTMENT, title: 'Căn hộ Midtown Phú Mỹ Hưng 2PN giá tốt', description: 'Căn hộ 2PN Midtown The Peak, Phú Mỹ Hưng. Nội thất cơ bản, view đẹp.', address: '12 Nguyễn Lương Bằng', ward: 'Tân Phú', district: 'Quận 7', city: 'Hồ Chí Minh', lat: 10.7285, lng: 106.7195, areaM2: 85, usableAreaM2: 78, bedrooms: 2, bathrooms: 2, floor: 18, totalFloors: 30, direction: Direction.EAST, yearBuilt: 2021, legalStatus: 'SO_HONG' as const, projectName: 'Midtown Phú Mỹ Hưng', amenities: '["hồ bơi","gym","công viên"]' }, { id: 'seed-prop-010', propertyType: PropertyType.TOWNHOUSE, title: 'Nhà phố Gò Vấp 1 trệt 2 lầu sổ hồng riêng', description: 'Nhà phố mới xây tại Gò Vấp, 1 trệt 2 lầu, sân thượng, hẻm xe hơi 6m.', address: '88 Nguyễn Oanh', ward: 'Phường 17', district: 'Quận Gò Vấp', city: 'Hồ Chí Minh', lat: 10.8352, lng: 106.6648, areaM2: 65, usableAreaM2: 150, bedrooms: 3, bathrooms: 3, floors: 3, direction: Direction.WEST, yearBuilt: 2024, legalStatus: 'SO_HONG' as const, projectName: null, amenities: '["sân thượng","ban công"]' }, ]; @@ -629,7 +629,7 @@ async function seedSavedSearches() { console.log('🔍 Seeding saved searches...'); const searches = [ { id: 'seed-search-001', userId: 'seed-buyer-001', name: 'Căn hộ Quận 1 dưới 10 tỷ', filters: { district: 'Quận 1', propertyType: 'APARTMENT', priceMax: 10000000000, bedrooms: 2 }, alertEnabled: true }, - { id: 'seed-search-002', userId: 'seed-buyer-001', name: 'Nhà phố Thủ Đức', filters: { district: 'Thủ Đức', propertyType: 'TOWNHOUSE', transactionType: 'SALE' }, alertEnabled: true }, + { id: 'seed-search-002', userId: 'seed-buyer-001', name: 'Nhà phố Thủ Đức', filters: { district: 'Thành phố Thủ Đức', propertyType: 'TOWNHOUSE', transactionType: 'SALE' }, alertEnabled: true }, { id: 'seed-search-003', userId: 'seed-buyer-002', name: 'Cho thuê gần Metro', filters: { transactionType: 'RENT', priceMax: 20000000, metroDistanceMax: 1000 }, alertEnabled: true }, { id: 'seed-search-004', userId: 'seed-buyer-002', name: 'Đất nền Quận 7 đầu tư', filters: { district: 'Quận 7', propertyType: 'LAND', priceMax: 15000000000 }, alertEnabled: false }, ];