Files
goodgo-platform/apps/web/components/comparison/comparison-stats.tsx
Ho Ngoc Hai 37fab515b7 feat(web): add property comparison page with side-by-side view
Build a complete property comparison feature at /compare:
- Zustand store with localStorage persistence for selected listings (2-5)
- Side-by-side comparison table (price, area, price/m², amenities, location, etc.)
- Summary statistics banner (price range, area range, price/m² range)
- "Add to Compare" button on property cards and detail pages
- Floating comparison bar for quick access when listings are selected
- Bilingual i18n support (Vietnamese + English)
- 18 unit tests for store logic and comparison stats computation
- Mobile-responsive layout with horizontal scroll on comparison table

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-10 23:55:50 +07:00

69 lines
2.2 KiB
TypeScript

'use client';
import { useTranslations } from 'next-intl';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import type { ComparisonStats } from '@/lib/comparison-store';
import { formatPrice, formatPricePerM2 } from '@/lib/currency';
interface ComparisonStatsBannerProps {
stats: ComparisonStats;
}
export function ComparisonStatsBanner({ stats }: ComparisonStatsBannerProps) {
const t = useTranslations('compare');
return (
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">
{t('priceRange')}
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-lg font-bold text-primary">
{formatPrice(stats.priceRange.min)} {formatPrice(stats.priceRange.max)}
</p>
<p className="mt-1 text-sm text-muted-foreground">
{t('average')}: {formatPrice(stats.priceRange.avg)} VND
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">
{t('areaRange')}
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-lg font-bold">
{stats.areaRange.min} {stats.areaRange.max} m²
</p>
<p className="mt-1 text-sm text-muted-foreground">
{t('average')}: {stats.areaRange.avg} m²
</p>
</CardContent>
</Card>
{stats.pricePerM2Range && (
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">
{t('pricePerM2Range')}
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-lg font-bold">
{formatPricePerM2(stats.pricePerM2Range.min)} {formatPricePerM2(stats.pricePerM2Range.max)}
</p>
<p className="mt-1 text-sm text-muted-foreground">
{t('average')}: {formatPricePerM2(stats.pricePerM2Range.avg)}
</p>
</CardContent>
</Card>
)}
</div>
);
}