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:
74
apps/web/app/(dashboard)/dashboard/valuation/page.tsx
Normal file
74
apps/web/app/(dashboard)/dashboard/valuation/page.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { ValuationForm } from '@/components/valuation/valuation-form';
|
||||
import { ValuationHistory } from '@/components/valuation/valuation-history';
|
||||
import { ValuationResults } from '@/components/valuation/valuation-results';
|
||||
import {
|
||||
useValuationPredict,
|
||||
useValuationHistory,
|
||||
useValuationDetail,
|
||||
} from '@/lib/hooks/use-valuation';
|
||||
import type { ValuationRequest, ValuationResult } from '@/lib/valuation-api';
|
||||
|
||||
export default function ValuationPage() {
|
||||
const [historyPage, setHistoryPage] = useState(1);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
|
||||
const predictMutation = useValuationPredict();
|
||||
const { data: historyData, isLoading: historyLoading } = useValuationHistory(historyPage);
|
||||
const { data: selectedResult } = useValuationDetail(selectedId ?? '');
|
||||
|
||||
const currentResult: ValuationResult | undefined =
|
||||
predictMutation.data ?? selectedResult;
|
||||
|
||||
const handleSubmit = (data: ValuationRequest) => {
|
||||
setSelectedId(null);
|
||||
predictMutation.mutate(data);
|
||||
};
|
||||
|
||||
const handleSelectHistory = (id: string) => {
|
||||
setSelectedId(id);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">Dinh gia AI</h1>
|
||||
<p className="mt-2 text-muted-foreground">
|
||||
Su dung AI de uoc tinh gia tri bat dong san dua tren du lieu thi truong
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 lg:grid-cols-3">
|
||||
{/* Form + Results */}
|
||||
<div className="space-y-6 lg:col-span-2">
|
||||
<ValuationForm
|
||||
onSubmit={handleSubmit}
|
||||
isLoading={predictMutation.isPending}
|
||||
/>
|
||||
|
||||
{predictMutation.isError && (
|
||||
<div className="rounded-lg border border-destructive/50 bg-destructive/10 p-4 text-sm text-destructive">
|
||||
Khong the dinh gia. Vui long thu lai sau.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{currentResult && <ValuationResults result={currentResult} />}
|
||||
</div>
|
||||
|
||||
{/* History sidebar */}
|
||||
<div>
|
||||
<ValuationHistory
|
||||
items={historyData?.data ?? []}
|
||||
total={historyData?.total ?? 0}
|
||||
page={historyPage}
|
||||
onPageChange={setHistoryPage}
|
||||
onSelect={handleSelectHistory}
|
||||
isLoading={historyLoading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -12,6 +12,7 @@ const navItems = [
|
||||
{ href: '/listings', label: 'Tin đăng', icon: '📋' },
|
||||
{ href: '/listings/new', label: 'Đăng tin', icon: '➕' },
|
||||
{ href: '/analytics', label: 'Phân tích', icon: '📊' },
|
||||
{ href: '/dashboard/valuation', label: 'Định giá AI', icon: '🤖' },
|
||||
{ href: '/dashboard/profile', label: 'Hồ sơ', icon: '👤' },
|
||||
{ href: '/dashboard/subscription', label: 'Gói dịch vụ', icon: '💎' },
|
||||
{ href: '/dashboard/payments', label: 'Thanh toán', icon: '💳' },
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ImageGallery } from '@/components/listings/image-gallery';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { AiEstimateButton } from '@/components/valuation/ai-estimate-button';
|
||||
import { listingsApi, type ListingDetail } from '@/lib/listings-api';
|
||||
import { PROPERTY_TYPES, DIRECTIONS, TRANSACTION_TYPES } from '@/lib/validations/listings';
|
||||
|
||||
@@ -264,6 +265,9 @@ export default function PublicListingDetailPage() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* AI Estimate */}
|
||||
<AiEstimateButton listingId={listing.id} />
|
||||
|
||||
{/* Stats */}
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
|
||||
Reference in New Issue
Block a user