231 lines
7.5 KiB
TypeScript
231 lines
7.5 KiB
TypeScript
|
|
'use client'
|
||
|
|
|
||
|
|
import {
|
||
|
|
PieChart,
|
||
|
|
Pie,
|
||
|
|
Cell,
|
||
|
|
Tooltip,
|
||
|
|
ResponsiveContainer,
|
||
|
|
Legend,
|
||
|
|
BarChart,
|
||
|
|
Bar,
|
||
|
|
XAxis,
|
||
|
|
YAxis,
|
||
|
|
CartesianGrid,
|
||
|
|
} from 'recharts'
|
||
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||
|
|
import { Badge } from '@/components/ui/badge'
|
||
|
|
|
||
|
|
interface DiversityData {
|
||
|
|
total: number
|
||
|
|
byCountry: { country: string; count: number; percentage: number }[]
|
||
|
|
byCategory: { category: string; count: number; percentage: number }[]
|
||
|
|
byOceanIssue: { issue: string; count: number; percentage: number }[]
|
||
|
|
byTag: { tag: string; count: number; percentage: number }[]
|
||
|
|
}
|
||
|
|
|
||
|
|
interface DiversityMetricsProps {
|
||
|
|
data: DiversityData
|
||
|
|
}
|
||
|
|
|
||
|
|
const PIE_COLORS = [
|
||
|
|
'#053d57', '#de0f1e', '#557f8c', '#f38a52', '#6ad82f',
|
||
|
|
'#3be31e', '#c9c052', '#e6382f', '#ed6141', '#0bd90f',
|
||
|
|
'#8884d8', '#82ca9d', '#ffc658', '#ff7c7c', '#8dd1e1',
|
||
|
|
]
|
||
|
|
|
||
|
|
export function DiversityMetricsChart({ data }: DiversityMetricsProps) {
|
||
|
|
if (data.total === 0) {
|
||
|
|
return (
|
||
|
|
<Card>
|
||
|
|
<CardContent className="flex items-center justify-center py-12">
|
||
|
|
<p className="text-muted-foreground">No project data available</p>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Top countries for pie chart (max 10, others grouped)
|
||
|
|
const topCountries = data.byCountry.slice(0, 10)
|
||
|
|
const otherCountries = data.byCountry.slice(10)
|
||
|
|
const countryPieData = otherCountries.length > 0
|
||
|
|
? [...topCountries, {
|
||
|
|
country: 'Others',
|
||
|
|
count: otherCountries.reduce((sum, c) => sum + c.count, 0),
|
||
|
|
percentage: otherCountries.reduce((sum, c) => sum + c.percentage, 0),
|
||
|
|
}]
|
||
|
|
: topCountries
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="space-y-6">
|
||
|
|
{/* Summary */}
|
||
|
|
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||
|
|
<Card>
|
||
|
|
<CardContent className="pt-6">
|
||
|
|
<div className="text-2xl font-bold">{data.total}</div>
|
||
|
|
<p className="text-sm text-muted-foreground">Total Projects</p>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
<Card>
|
||
|
|
<CardContent className="pt-6">
|
||
|
|
<div className="text-2xl font-bold">{data.byCountry.length}</div>
|
||
|
|
<p className="text-sm text-muted-foreground">Countries Represented</p>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
<Card>
|
||
|
|
<CardContent className="pt-6">
|
||
|
|
<div className="text-2xl font-bold">{data.byCategory.length}</div>
|
||
|
|
<p className="text-sm text-muted-foreground">Categories</p>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
<Card>
|
||
|
|
<CardContent className="pt-6">
|
||
|
|
<div className="text-2xl font-bold">{data.byTag.length}</div>
|
||
|
|
<p className="text-sm text-muted-foreground">Unique Tags</p>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="grid gap-6 lg:grid-cols-2">
|
||
|
|
{/* Country Distribution */}
|
||
|
|
<Card>
|
||
|
|
<CardHeader>
|
||
|
|
<CardTitle>Geographic Distribution</CardTitle>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent>
|
||
|
|
<div className="h-[350px]">
|
||
|
|
<ResponsiveContainer width="100%" height="100%">
|
||
|
|
<PieChart>
|
||
|
|
<Pie
|
||
|
|
data={countryPieData}
|
||
|
|
cx="50%"
|
||
|
|
cy="50%"
|
||
|
|
innerRadius={60}
|
||
|
|
outerRadius={120}
|
||
|
|
paddingAngle={2}
|
||
|
|
dataKey="count"
|
||
|
|
nameKey="country"
|
||
|
|
label
|
||
|
|
>
|
||
|
|
{countryPieData.map((_, index) => (
|
||
|
|
<Cell key={`cell-${index}`} fill={PIE_COLORS[index % PIE_COLORS.length]} />
|
||
|
|
))}
|
||
|
|
</Pie>
|
||
|
|
<Tooltip
|
||
|
|
contentStyle={{
|
||
|
|
backgroundColor: 'hsl(var(--card))',
|
||
|
|
border: '1px solid hsl(var(--border))',
|
||
|
|
borderRadius: '6px',
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
</PieChart>
|
||
|
|
</ResponsiveContainer>
|
||
|
|
</div>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
|
||
|
|
{/* Category Distribution */}
|
||
|
|
<Card>
|
||
|
|
<CardHeader>
|
||
|
|
<CardTitle>Competition Categories</CardTitle>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent>
|
||
|
|
{data.byCategory.length > 0 ? (
|
||
|
|
<div className="h-[350px]">
|
||
|
|
<ResponsiveContainer width="100%" height="100%">
|
||
|
|
<BarChart
|
||
|
|
data={data.byCategory.slice(0, 10)}
|
||
|
|
layout="vertical"
|
||
|
|
margin={{ top: 5, right: 30, bottom: 5, left: 100 }}
|
||
|
|
>
|
||
|
|
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
|
||
|
|
<XAxis type="number" />
|
||
|
|
<YAxis
|
||
|
|
type="category"
|
||
|
|
dataKey="category"
|
||
|
|
width={90}
|
||
|
|
tick={{ fontSize: 12 }}
|
||
|
|
/>
|
||
|
|
<Tooltip
|
||
|
|
contentStyle={{
|
||
|
|
backgroundColor: 'hsl(var(--card))',
|
||
|
|
border: '1px solid hsl(var(--border))',
|
||
|
|
borderRadius: '6px',
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
<Bar dataKey="count" fill="#053d57" radius={[0, 4, 4, 0]} />
|
||
|
|
</BarChart>
|
||
|
|
</ResponsiveContainer>
|
||
|
|
</div>
|
||
|
|
) : (
|
||
|
|
<p className="text-muted-foreground text-center py-8">No category data</p>
|
||
|
|
)}
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Ocean Issues */}
|
||
|
|
{data.byOceanIssue.length > 0 && (
|
||
|
|
<Card>
|
||
|
|
<CardHeader>
|
||
|
|
<CardTitle>Ocean Issues Addressed</CardTitle>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent>
|
||
|
|
<div className="h-[350px]">
|
||
|
|
<ResponsiveContainer width="100%" height="100%">
|
||
|
|
<BarChart
|
||
|
|
data={data.byOceanIssue.slice(0, 15)}
|
||
|
|
margin={{ top: 20, right: 30, bottom: 60, left: 20 }}
|
||
|
|
>
|
||
|
|
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
|
||
|
|
<XAxis
|
||
|
|
dataKey="issue"
|
||
|
|
angle={-35}
|
||
|
|
textAnchor="end"
|
||
|
|
height={80}
|
||
|
|
tick={{ fontSize: 11 }}
|
||
|
|
/>
|
||
|
|
<YAxis />
|
||
|
|
<Tooltip
|
||
|
|
contentStyle={{
|
||
|
|
backgroundColor: 'hsl(var(--card))',
|
||
|
|
border: '1px solid hsl(var(--border))',
|
||
|
|
borderRadius: '6px',
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
<Bar dataKey="count" fill="#557f8c" radius={[4, 4, 0, 0]} />
|
||
|
|
</BarChart>
|
||
|
|
</ResponsiveContainer>
|
||
|
|
</div>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* Tags Cloud */}
|
||
|
|
{data.byTag.length > 0 && (
|
||
|
|
<Card>
|
||
|
|
<CardHeader>
|
||
|
|
<CardTitle>Project Tags</CardTitle>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent>
|
||
|
|
<div className="flex flex-wrap gap-2">
|
||
|
|
{data.byTag.slice(0, 30).map((tag) => (
|
||
|
|
<Badge
|
||
|
|
key={tag.tag}
|
||
|
|
variant="secondary"
|
||
|
|
className="text-sm"
|
||
|
|
style={{
|
||
|
|
fontSize: `${Math.max(0.7, Math.min(1.4, 0.7 + tag.percentage / 20))}rem`,
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{tag.tag} ({tag.count})
|
||
|
|
</Badge>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|