Files
goodgo-platform/apps/web/components/valuation/valuation-results.tsx
Ho Ngoc Hai ccfc176e40 fix: valuation page Vietnamese diacritics, correct API routes, update tests
- Add proper Vietnamese diacritics to all valuation components
  (form, results, history) and their test assertions
- Fix valuation API client to use /analytics/valuation endpoint
- Return empty history gracefully (no server endpoint yet)

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
2026-04-13 12:03:47 +07:00

143 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import type { ValuationResult } from '@/lib/valuation-api';
interface ValuationResultsProps {
result: ValuationResult;
}
function formatPrice(num: number): string {
if (num >= 1_000_000_000) return `${(num / 1_000_000_000).toFixed(2)} 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²`;
}
export function ValuationResults({ result }: ValuationResultsProps) {
const confidencePct = Math.round(result.confidence * 100);
return (
<div className="space-y-6">
{/* Main estimate */}
<Card className="border-primary/20 bg-primary/5">
<CardHeader className="pb-3">
<CardDescription>Giá ưc tính bởi AI</CardDescription>
<CardTitle className="text-3xl text-primary">
{formatPrice(result.estimatedPriceVND)} VNĐ
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid gap-4 sm:grid-cols-3">
<div>
<p className="text-sm text-muted-foreground">Đ tin cậy</p>
<div className="mt-1 flex items-center gap-2">
<div className="h-2 flex-1 rounded-full bg-muted">
<div
className="h-2 rounded-full bg-primary transition-all"
style={{ width: `${confidencePct}%` }}
/>
</div>
<span className="text-sm font-medium">{confidencePct}%</span>
</div>
</div>
<div>
<p className="text-sm text-muted-foreground">Giá/m²</p>
<p className="mt-1 text-lg font-semibold">{formatPriceM2(result.pricePerM2)}</p>
</div>
<div>
<p className="text-sm text-muted-foreground">Khoảng giá</p>
<p className="mt-1 text-lg font-semibold">
{formatPrice(result.priceRangeLow)} {formatPrice(result.priceRangeHigh)}
</p>
</div>
</div>
</CardContent>
</Card>
{/* Price drivers */}
{result.priceDrivers.length > 0 && (
<Card>
<CardHeader>
<CardTitle className="text-lg">Yếu tố nh hưởng giá</CardTitle>
<CardDescription>Các yếu tố chính tác đng đến giá trị bất đng sản</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-3">
{result.priceDrivers.map((driver) => (
<div key={driver.feature} className="flex items-center gap-3">
<span
className={`text-sm font-medium ${
driver.direction === 'positive' ? 'text-green-600' : 'text-red-600'
}`}
>
{driver.direction === 'positive' ? '+' : '-'}
{Math.abs(driver.impact).toFixed(1)}%
</span>
<div className="flex-1">
<div className="flex items-center gap-2">
<span className="text-sm">{driver.feature}</span>
</div>
<div className="mt-1 h-1.5 rounded-full bg-muted">
<div
className={`h-1.5 rounded-full ${
driver.direction === 'positive' ? 'bg-green-500' : 'bg-red-500'
}`}
style={{ width: `${Math.min(Math.abs(driver.impact), 100)}%` }}
/>
</div>
</div>
</div>
))}
</div>
</CardContent>
</Card>
)}
{/* Comparables */}
{result.comparables.length > 0 && (
<Card>
<CardHeader>
<CardTitle className="text-lg">Bất đng sản tương tự</CardTitle>
<CardDescription>
{result.comparables.length} bất đng sản đc điểm tương tự trong khu vực
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-3">
{result.comparables.map((comp) => (
<div
key={comp.id}
className="flex items-center gap-4 rounded-lg border p-3"
>
<div className="min-w-0 flex-1">
<p className="truncate font-medium">{comp.title}</p>
<p className="text-sm text-muted-foreground">
{comp.district} &middot; {comp.areaM2} m²
</p>
</div>
<div className="text-right">
<p className="font-semibold text-primary">{formatPrice(Number(comp.priceVND))}</p>
<p className="text-sm text-muted-foreground">
{formatPriceM2(comp.pricePerM2)}
</p>
</div>
<div className="hidden sm:block">
<span className="rounded-full bg-accent px-2 py-1 text-xs font-medium">
{Math.round(comp.similarity * 100)}% tương tự
</span>
</div>
</div>
))}
</div>
</CardContent>
</Card>
)}
</div>
);
}