Skip to content

Commit 6336a46

Browse files
Merge pull request #577 from CodebilityDev/admin-dashboard-layoutandcharts
Made enhancements on layout and charts design with tooltips/animations.
2 parents 35d82bf + 9b008e5 commit 6336a46

5 files changed

Lines changed: 553 additions & 162 deletions

File tree

‎apps/codebility/app/home/admin-dashboard/_components/AdminDashboardApplicantStatusPie.tsx‎

Lines changed: 117 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -12,88 +12,167 @@ import {
1212
ChartContainer,
1313
ChartLegend,
1414
ChartLegendContent,
15+
ChartTooltip,
16+
ChartTooltipContent,
1517
} from "@/components/ui/chart";
16-
import { Pie, PieChart } from "recharts";
1718
import { cn } from "@/lib/utils";
19+
import { Cell, Pie, PieChart } from "recharts";
1820

1921
interface ApplicantStatusCounts extends Record<string, number> {}
2022

2123
interface AdminDashboardApplicantStatusPieProps {
2224
data?: ApplicantStatusCounts;
2325
}
2426

27+
// Distinct, accessible colors per status — avoids the all-blue sameness
28+
const STATUS_COLORS: Record<string, string> = {
29+
applying: "#3b82f6", // blue
30+
testing: "#f59e0b", // amber
31+
onboarding: "#10b981", // emerald
32+
passed: "#8b5cf6", // violet
33+
};
34+
35+
// Fallback for any unexpected statuses
36+
const FALLBACK_COLORS = ["#ec4899", "#14b8a6", "#f97316", "#6366f1"];
37+
2538
export default function AdminDashboardApplicantStatusPie({
2639
data,
2740
}: AdminDashboardApplicantStatusPieProps) {
2841
const title = "Total Applicant Status";
29-
30-
if (!data)
31-
return (
32-
<Card className={cn(
42+
43+
const emptyCard = (
44+
<Card
45+
className={cn(
3346
"flex h-full flex-col relative overflow-hidden",
3447
"bg-white dark:bg-gradient-to-br dark:from-blue-950 dark:via-slate-900 dark:to-slate-950",
35-
"border-gray-200 dark:border-blue-900/30"
36-
)}>
37-
<div className="absolute inset-0 bg-gradient-to-br to-transparent pointer-events-none opacity-0 dark:opacity-100 dark:from-blue-500/10" />
38-
39-
<CardHeader className="relative items-center pb-0">
40-
<CardTitle className="text-lg">{title}</CardTitle>
41-
</CardHeader>
42-
<CardContent className="relative flex flex-1 items-center justify-center">
43-
<p className="text-muted-foreground">No data available</p>
44-
</CardContent>
45-
</Card>
46-
);
47-
48-
const chartData = Object.keys(data || {}).map((status) => ({
48+
"border-gray-200 dark:border-blue-900/30",
49+
)}
50+
>
51+
<div className="absolute inset-0 bg-gradient-to-br to-transparent pointer-events-none opacity-0 dark:opacity-100 dark:from-blue-500/10" />
52+
<CardHeader className="relative items-center pb-0">
53+
<CardTitle className="text-lg">{title}</CardTitle>
54+
</CardHeader>
55+
<CardContent className="relative flex flex-1 items-center justify-center">
56+
<p className="text-muted-foreground">No data available</p>
57+
</CardContent>
58+
</Card>
59+
);
60+
61+
if (!data) return emptyCard;
62+
63+
const total = Object.values(data).reduce((sum, v) => sum + v, 0);
64+
65+
if (total === 0) return emptyCard;
66+
67+
const chartData = Object.keys(data).map((status, index) => ({
4968
status,
5069
applicants: data[status],
51-
fill: `var(--color-${status})`,
70+
percentage: total > 0 ? (((data[status] ?? 0) / total) * 100).toFixed(1) : "0",
71+
fill: STATUS_COLORS[status] ?? FALLBACK_COLORS[index % FALLBACK_COLORS.length],
5272
}));
5373

54-
const chartConfig = Object.keys(data || {}).reduce(
55-
(config, status) => {
56-
const capitalizedLabel = status.charAt(0).toUpperCase() + status.slice(1);
74+
const chartConfig = Object.keys(data).reduce(
75+
(config, status, index) => {
5776
config[status] = {
58-
label: capitalizedLabel,
59-
color: `hsl(var(--chart-${Object.keys(config).length + 1}))`,
77+
label: status.charAt(0).toUpperCase() + status.slice(1),
78+
color: STATUS_COLORS[status] ?? FALLBACK_COLORS[index % FALLBACK_COLORS.length],
6079
};
6180
return config;
6281
},
63-
{} as Record<string, { label: string; color: string }>,
82+
{} as ChartConfig,
6483
);
6584

6685
return (
67-
<Card className={cn(
68-
"flex h-full flex-col relative overflow-hidden",
69-
"bg-white dark:bg-gradient-to-br dark:from-blue-950 dark:via-slate-900 dark:to-slate-950",
70-
"border-gray-200 dark:border-blue-900/30"
71-
)}>
86+
<Card
87+
className={cn(
88+
"flex h-full flex-col relative overflow-hidden",
89+
"bg-white dark:bg-gradient-to-br dark:from-blue-950 dark:via-slate-900 dark:to-slate-950",
90+
"border-gray-200 dark:border-blue-900/30",
91+
)}
92+
>
7293
<div className="absolute inset-0 bg-gradient-to-br to-transparent pointer-events-none opacity-0 dark:opacity-100 dark:from-blue-500/10" />
73-
94+
7495
<CardHeader className="relative items-center pb-0">
7596
<CardTitle className="text-lg">{title}</CardTitle>
7697
<CardDescription className="text-center text-sm">
7798
Distribution of application statuses
7899
</CardDescription>
79100
</CardHeader>
80-
<CardContent className="relative flex-1 pb-0">
101+
102+
<CardContent className="relative flex-1 pb-6">
81103
<ChartContainer
82104
config={chartConfig}
83-
className="mx-auto aspect-square max-h-[350px]"
105+
className="mx-auto aspect-square max-h-[280px]"
84106
>
85107
<PieChart>
86-
<Pie
87-
data={chartData}
88-
dataKey="applicants"
89-
strokeWidth={2}
108+
{/* Tooltip: shows status label, count, and percentage */}
109+
<ChartTooltip
110+
content={
111+
<ChartTooltipContent
112+
formatter={(value, name) => {
113+
const entry = chartData.find((d) => d.status === name);
114+
const percentage = entry?.percentage ?? "0";
115+
return (
116+
<div className="flex flex-col gap-0.5">
117+
<span className="font-semibold capitalize">{name}</span>
118+
<span>
119+
{value} applicants ({percentage}%)
120+
</span>
121+
</div>
122+
);
123+
}}
124+
hideLabel
125+
/>
126+
}
90127
/>
128+
129+
{/* Donut chart — inner radius reveals the total count in the center */}
130+
<Pie
131+
data={chartData}
132+
dataKey="applicants"
133+
nameKey="status"
134+
innerRadius="50%"
135+
outerRadius="75%"
136+
strokeWidth={3}
137+
stroke="transparent"
138+
paddingAngle={3}
139+
labelLine={false}
140+
label={({ cx, cy }) => (
141+
<g>
142+
<text
143+
x={cx}
144+
y={cy - 8}
145+
textAnchor="middle"
146+
dominantBaseline="middle"
147+
className="fill-foreground"
148+
style={{ fontSize: "1.5rem", fontWeight: 700 }}
149+
>
150+
{total.toLocaleString()}
151+
</text>
152+
<text
153+
x={cx}
154+
y={cy + 14}
155+
textAnchor="middle"
156+
dominantBaseline="middle"
157+
style={{ fontSize: "0.75rem", fill: "hsl(var(--muted-foreground))" }}
158+
>
159+
Total
160+
</text>
161+
</g>
162+
)}
163+
>
164+
{chartData.map((entry) => (
165+
<Cell key={entry.status} fill={entry.fill} />
166+
))}
167+
</Pie>
168+
91169
<ChartLegend
92170
content={<ChartLegendContent nameKey="status" />}
93171
className="-translate-y-2 flex-wrap gap-2 [&>*]:basis-1/4 [&>*]:justify-center"
94172
/>
95173
</PieChart>
96174
</ChartContainer>
175+
97176
</CardContent>
98177
</Card>
99178
);

0 commit comments

Comments
 (0)