Initial commit: MOPC platform with Docker deployment setup
Full Next.js 15 platform with tRPC, Prisma, PostgreSQL, NextAuth. Includes production Dockerfile (multi-stage, port 7600), docker-compose with registry-based image pull, Gitea Actions CI workflow, nginx config for portal.monaco-opc.com, deployment scripts, and DEPLOYMENT.md guide. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
131
src/components/charts/status-breakdown.tsx
Normal file
131
src/components/charts/status-breakdown.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
'use client'
|
||||
import {
|
||||
PieChart,
|
||||
Pie,
|
||||
Cell,
|
||||
Tooltip,
|
||||
Legend,
|
||||
ResponsiveContainer,
|
||||
} from 'recharts'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
|
||||
interface StatusDataPoint {
|
||||
status: string
|
||||
count: number
|
||||
}
|
||||
|
||||
interface StatusBreakdownProps {
|
||||
data: StatusDataPoint[]
|
||||
}
|
||||
|
||||
const STATUS_COLORS: Record<string, string> = {
|
||||
PENDING: '#8884d8',
|
||||
UNDER_REVIEW: '#82ca9d',
|
||||
SHORTLISTED: '#ffc658',
|
||||
SEMIFINALIST: '#ff7300',
|
||||
FINALIST: '#00C49F',
|
||||
WINNER: '#0088FE',
|
||||
ELIMINATED: '#de0f1e',
|
||||
WITHDRAWN: '#999999',
|
||||
}
|
||||
|
||||
const renderCustomLabel = ({
|
||||
cx,
|
||||
cy,
|
||||
midAngle,
|
||||
innerRadius,
|
||||
outerRadius,
|
||||
percent,
|
||||
}: {
|
||||
cx?: number
|
||||
cy?: number
|
||||
midAngle?: number
|
||||
innerRadius?: number
|
||||
outerRadius?: number
|
||||
percent?: number
|
||||
}) => {
|
||||
if (cx === undefined || cy === undefined || midAngle === undefined ||
|
||||
innerRadius === undefined || outerRadius === undefined || percent === undefined) {
|
||||
return null
|
||||
}
|
||||
if (percent < 0.05) return null // Don't show labels for small slices
|
||||
|
||||
const RADIAN = Math.PI / 180
|
||||
const radius = innerRadius + (outerRadius - innerRadius) * 0.5
|
||||
const x = cx + radius * Math.cos(-midAngle * RADIAN)
|
||||
const y = cy + radius * Math.sin(-midAngle * RADIAN)
|
||||
|
||||
return (
|
||||
<text
|
||||
x={x}
|
||||
y={y}
|
||||
fill="white"
|
||||
textAnchor={x > cx ? 'start' : 'end'}
|
||||
dominantBaseline="central"
|
||||
fontSize={12}
|
||||
fontWeight={600}
|
||||
>
|
||||
{`${(percent * 100).toFixed(0)}%`}
|
||||
</text>
|
||||
)
|
||||
}
|
||||
|
||||
export function StatusBreakdownChart({ data }: StatusBreakdownProps) {
|
||||
const total = data.reduce((sum, item) => sum + item.count, 0)
|
||||
|
||||
// Format status for display
|
||||
const formattedData = data.map((d) => ({
|
||||
...d,
|
||||
name: d.status.replace(/_/g, ' '),
|
||||
color: STATUS_COLORS[d.status] || '#8884d8',
|
||||
}))
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center justify-between">
|
||||
<span>Project Status Distribution</span>
|
||||
<span className="text-sm font-normal text-muted-foreground">
|
||||
{total} projects
|
||||
</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="h-[300px]">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<PieChart>
|
||||
<Pie
|
||||
data={formattedData}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
labelLine={false}
|
||||
label={renderCustomLabel}
|
||||
outerRadius={100}
|
||||
innerRadius={50}
|
||||
fill="#8884d8"
|
||||
dataKey="count"
|
||||
nameKey="name"
|
||||
>
|
||||
{formattedData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: 'hsl(var(--card))',
|
||||
border: '1px solid hsl(var(--border))',
|
||||
borderRadius: '6px',
|
||||
}}
|
||||
formatter={(value: number | undefined, name: string | undefined) => [
|
||||
`${value ?? 0} (${(((value ?? 0) / total) * 100).toFixed(1)}%)`,
|
||||
name ?? '',
|
||||
]}
|
||||
/>
|
||||
<Legend />
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user