feat(web): add Property Valuation UI with AVM integration

Build the valuation page at /dashboard/valuation with form input,
AI-powered price estimation results, comparable properties display,
and valuation history. Add "Dinh gia AI" button to listing detail
sidebar for quick per-listing estimates.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-09 00:17:12 +07:00
parent 6f3e6998ac
commit 3c6ed4c82a
10 changed files with 792 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
import { apiClient } from './api-client';
// ─── Types ──────────────────────────────────────────────
export interface ValuationRequest {
propertyType: string;
area: number;
district: string;
city: string;
bedrooms?: number;
bathrooms?: number;
floors?: number;
frontage?: number;
roadWidth?: number;
yearBuilt?: number;
hasLegalPaper?: boolean;
}
export interface ValuationComparable {
id: string;
title: string;
address: string;
district: string;
priceVND: string;
areaM2: number;
pricePerM2: number;
similarity: number;
latitude?: number;
longitude?: number;
}
export interface PriceDriver {
feature: string;
impact: number;
direction: 'positive' | 'negative';
}
export interface ValuationResult {
id: string;
estimatedPriceVND: number;
confidence: number;
pricePerM2: number;
priceRangeLow: number;
priceRangeHigh: number;
comparables: ValuationComparable[];
priceDrivers: PriceDriver[];
modelVersion: string;
createdAt: string;
}
export interface ValuationHistoryItem {
id: string;
propertyType: string;
district: string;
city: string;
area: number;
estimatedPriceVND: number;
confidence: number;
createdAt: string;
}
export interface ValuationHistoryResponse {
data: ValuationHistoryItem[];
total: number;
page: number;
limit: number;
}
// ─── API ────────────────────────────────────────────────
export const valuationApi = {
predict: (data: ValuationRequest) =>
apiClient.post<ValuationResult>('/valuation/predict', data),
getHistory: (page = 1, limit = 10) =>
apiClient.get<ValuationHistoryResponse>(
`/valuation/history?page=${page}&limit=${limit}`,
),
getById: (id: string) =>
apiClient.get<ValuationResult>(`/valuation/${id}`),
predictForListing: (listingId: string) =>
apiClient.post<ValuationResult>(`/valuation/predict-listing/${listingId}`),
};