- Add grid/map view toggle on /du-an listing page with Mapbox project markers - Enhance du-an detail with master plan viewer, neighborhood radar chart, POI map, and price history chart - Create neighborhood component suite: radar chart (Recharts), POI map (Mapbox), score badges - Add du-an API client, server-side fetching, and React Query hooks - Wire NotificationBell into public layout header for authenticated users - Fix missing PROJECT_STATUS_COLORS import in du-an detail client Co-Authored-By: Paperclip <noreply@paperclip.ing>
104 lines
2.7 KiB
TypeScript
104 lines
2.7 KiB
TypeScript
'use client';
|
|
|
|
import {
|
|
Radar,
|
|
RadarChart,
|
|
PolarGrid,
|
|
PolarAngleAxis,
|
|
PolarRadiusAxis,
|
|
ResponsiveContainer,
|
|
Tooltip,
|
|
} from 'recharts';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import type { NeighborhoodCategory } from './types';
|
|
|
|
interface NeighborhoodRadarChartProps {
|
|
categories: NeighborhoodCategory[];
|
|
height?: number;
|
|
showBadges?: boolean;
|
|
className?: string;
|
|
}
|
|
|
|
function getScoreVariant(score: number): 'success' | 'warning' | 'destructive' {
|
|
if (score > 7) return 'success';
|
|
if (score >= 5) return 'warning';
|
|
return 'destructive';
|
|
}
|
|
|
|
function getScoreLabel(score: number): string {
|
|
if (score > 7) return 'Tốt';
|
|
if (score >= 5) return 'TB';
|
|
return 'Yếu';
|
|
}
|
|
|
|
export function NeighborhoodRadarChart({
|
|
categories,
|
|
height = 300,
|
|
showBadges = true,
|
|
className,
|
|
}: NeighborhoodRadarChartProps) {
|
|
const chartData = categories.map((cat) => ({
|
|
subject: cat.label,
|
|
score: cat.score,
|
|
fullMark: 10,
|
|
}));
|
|
|
|
return (
|
|
<div className={className}>
|
|
<ResponsiveContainer width="100%" height={height}>
|
|
<RadarChart cx="50%" cy="50%" outerRadius="75%" data={chartData}>
|
|
<PolarGrid
|
|
stroke="hsl(var(--border))"
|
|
strokeDasharray="3 3"
|
|
/>
|
|
<PolarAngleAxis
|
|
dataKey="subject"
|
|
tick={{
|
|
fontSize: 12,
|
|
fill: 'hsl(var(--muted-foreground))',
|
|
}}
|
|
/>
|
|
<PolarRadiusAxis
|
|
angle={90}
|
|
domain={[0, 10]}
|
|
tick={{ fontSize: 10, fill: 'hsl(var(--muted-foreground))' }}
|
|
tickCount={6}
|
|
/>
|
|
<Tooltip
|
|
contentStyle={{
|
|
backgroundColor: 'hsl(var(--card))',
|
|
border: '1px solid hsl(var(--border))',
|
|
borderRadius: '0.5rem',
|
|
fontSize: '0.875rem',
|
|
}}
|
|
formatter={(value) => [`${Number(value).toFixed(1)}/10`, 'Điểm']}
|
|
/>
|
|
<Radar
|
|
name="Điểm"
|
|
dataKey="score"
|
|
stroke="hsl(var(--primary))"
|
|
fill="hsl(var(--primary))"
|
|
fillOpacity={0.2}
|
|
strokeWidth={2}
|
|
/>
|
|
</RadarChart>
|
|
</ResponsiveContainer>
|
|
|
|
{showBadges && (
|
|
<div className="mt-3 flex flex-wrap items-center justify-center gap-2">
|
|
{categories.map((cat) => (
|
|
<Badge
|
|
key={cat.category}
|
|
variant={getScoreVariant(cat.score)}
|
|
className="gap-1 text-xs"
|
|
>
|
|
{cat.label}: {cat.score.toFixed(1)}
|
|
<span className="opacity-70">({getScoreLabel(cat.score)})</span>
|
|
</Badge>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|