Files
goodgo-platform/apps/web/components/valuation/market-context-card.tsx
Ho Ngoc Hai 8da488711b feat(analytics): AVM v2 batch valuation, comparison, history + frontend upgrade
Add batch valuation (POST /analytics/valuation/batch, max 50 properties),
valuation comparison (POST /analytics/valuation/compare, 2-5 properties),
and history endpoint (GET /analytics/valuation/history/:propertyId) with
confidence explanation helper. Frontend: enhanced valuation form with project
autocomplete and deep analysis toggle, results with confidence badges and
price range visualization, comparables table, history chart, market context
card, and PDF export.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-16 05:08:05 +07:00

96 lines
2.5 KiB
TypeScript

'use client';
import {
Building,
CalendarDays,
TrendingDown,
TrendingUp,
Users,
Warehouse,
} from 'lucide-react';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { formatPrice, formatPricePerM2 } from '@/lib/currency';
import type { MarketContext } from '@/lib/valuation-api';
interface MarketContextCardProps {
context: MarketContext;
}
export function MarketContextCard({ context }: MarketContextCardProps) {
const isGrowthPositive = context.priceGrowthYoY >= 0;
const stats = [
{
label: 'Giá trung bình/m²',
value: formatPricePerM2(context.avgPricePerM2),
icon: Building,
},
{
label: 'Giá trung vị',
value: formatPrice(context.medianPrice),
icon: Warehouse,
},
{
label: 'Tăng trưởng YoY',
value: `${isGrowthPositive ? '+' : ''}${context.priceGrowthYoY.toFixed(1)}%`,
icon: isGrowthPositive ? TrendingUp : TrendingDown,
color: isGrowthPositive ? 'text-green-600' : 'text-red-600',
},
{
label: 'Chỉ số nhu cầu',
value: `${context.demandIndex.toFixed(0)}/100`,
icon: Users,
},
{
label: 'Nguồn cung',
value: `${context.supplyCount.toLocaleString('vi-VN')} BĐS`,
icon: Building,
},
{
label: 'Thời gian bán TB',
value: `${context.avgDaysOnMarket} ngày`,
icon: CalendarDays,
},
];
return (
<Card>
<CardHeader>
<CardTitle className="text-lg">Bối cảnh thị trường</CardTitle>
<CardDescription>
{context.district}, {context.city} {context.period}
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{stats.map((stat) => {
const Icon = stat.icon;
return (
<div
key={stat.label}
className="flex items-start gap-3 rounded-lg border p-3"
>
<div className="rounded-md bg-muted p-2">
<Icon className="h-4 w-4 text-muted-foreground" />
</div>
<div>
<p className="text-xs text-muted-foreground">{stat.label}</p>
<p className={`text-sm font-semibold ${stat.color ?? ''}`}>
{stat.value}
</p>
</div>
</div>
);
})}
</div>
</CardContent>
</Card>
);
}