feat(web): integrate neighborhood radar chart into listing detail page
Add NeighborhoodRadarChart to listing detail view, fetching scores from the analytics API based on the listing's district and city. Displays a 6-axis radar chart (education, healthcare, transport, shopping, environment, safety) with overall score and color-coded badges. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -12,9 +12,15 @@ import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { AiEstimateButton } from '@/components/valuation/ai-estimate-button';
|
||||
import { formatPrice, formatPricePerM2 } from '@/lib/currency';
|
||||
import type { ListingDetail } from '@/lib/listings-api';
|
||||
import type { ListingDetail, NeighborhoodScoreResult } from '@/lib/listings-api';
|
||||
import { listingsApi } from '@/lib/listings-api';
|
||||
import { PROPERTY_TYPES, DIRECTIONS, TRANSACTION_TYPES } from '@/lib/validations/listings';
|
||||
|
||||
const NeighborhoodRadarChart = dynamic(
|
||||
() => import('@/components/neighborhood').then((m) => m.NeighborhoodRadarChart),
|
||||
{ ssr: false },
|
||||
);
|
||||
|
||||
const ListingMap = dynamic(
|
||||
() => import('@/components/map/listing-map').then((mod) => mod.ListingMap),
|
||||
{
|
||||
@@ -36,11 +42,31 @@ interface ListingDetailClientProps {
|
||||
listing: ListingDetail;
|
||||
}
|
||||
|
||||
function mapScoreToCategories(result: NeighborhoodScoreResult) {
|
||||
return [
|
||||
{ category: 'education', label: 'Giáo dục', score: result.educationScore },
|
||||
{ category: 'healthcare', label: 'Y tế', score: result.healthcareScore },
|
||||
{ category: 'transport', label: 'Giao thông', score: result.transportScore },
|
||||
{ category: 'shopping', label: 'Mua sắm', score: result.shoppingScore },
|
||||
{ category: 'environment', label: 'Môi trường', score: result.greeneryScore },
|
||||
{ category: 'safety', label: 'An ninh', score: result.safetyScore },
|
||||
];
|
||||
}
|
||||
|
||||
export function ListingDetailClient({ listing }: ListingDetailClientProps) {
|
||||
const { property, seller, agent } = listing;
|
||||
const transactionLabel = getLabel(TRANSACTION_TYPES, listing.transactionType);
|
||||
const propertyTypeLabel = getLabel(PROPERTY_TYPES, property.propertyType);
|
||||
const [inquiryOpen, setInquiryOpen] = React.useState(false);
|
||||
const [neighborhoodScore, setNeighborhoodScore] = React.useState<NeighborhoodScoreResult | null>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!property.district || !property.city) return;
|
||||
listingsApi
|
||||
.getNeighborhoodScore(property.district, property.city)
|
||||
.then(setNeighborhoodScore)
|
||||
.catch(() => {/* silently ignore — section simply won't render */});
|
||||
}, [property.district, property.city]);
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-6xl px-4 py-6">
|
||||
@@ -174,6 +200,27 @@ export function ListingDetailClient({ listing }: ListingDetailClientProps) {
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Neighborhood Score Radar Chart */}
|
||||
{neighborhoodScore && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Đánh giá khu vực</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="mb-3 flex items-center gap-2">
|
||||
<span className="text-2xl font-bold text-primary">
|
||||
{neighborhoodScore.totalScore.toFixed(1)}
|
||||
</span>
|
||||
<span className="text-sm text-muted-foreground">/10 điểm tổng</span>
|
||||
</div>
|
||||
<NeighborhoodRadarChart
|
||||
categories={mapScoreToCategories(neighborhoodScore)}
|
||||
height={300}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Sidebar */}
|
||||
|
||||
Reference in New Issue
Block a user