Skip to content

Commit 7a9600b

Browse files
authored
Harsh/analytics dashboard (#180)
1 parent 6242cc5 commit 7a9600b

17 files changed

+921
-332
lines changed

Diff for: public/images/dashboard.png

-1.48 KB
Binary file not shown.

Diff for: src/components/SideBar/Roles/admin.tsx

+16-2
Original file line numberDiff line numberDiff line change
@@ -295,9 +295,23 @@ const AdminDashboard = () => {
295295
</Link>
296296
<Link href={"/admin/dashboard"}>
297297
<div className="hover:bg-gray-900 rounded-md my-[1vh] py-[1vh] px-[1vw] cursor-pointer">
298-
<div className="flex justify-start gap-[1vw]">
298+
<div className="flex justify-start gap-[1vw]">
299299
<div className="w-[2vw]">
300-
<img width="20" height="20" src="/portal/images/dashboard.png" alt="dashboard"/>
300+
<svg
301+
xmlns="http://www.w3.org/2000/svg"
302+
viewBox="0 0 32 32"
303+
width="20"
304+
height="20"
305+
>
306+
<path
307+
fill="currentColor"
308+
d="M24 21h2v5h-2zm-4-5h2v10h-2zm-9 10a5.006 5.006 0 0 1-5-5h2a3 3 0 1 0 3-3v-2a5 5 0 0 1 0 10"
309+
></path>
310+
<path
311+
fill="currentColor"
312+
d="M28 2H4a2 2 0 0 0-2 2v24a2 2 0 0 0 2 2h24a2.003 2.003 0 0 0 2-2V4a2 2 0 0 0-2-2m0 9H14V4h14ZM12 4v7H4V4ZM4 28V13h24l.002 15Z"
313+
></path>
314+
</svg>
301315
</div>
302316
<motion.div
303317
initial={{ opacity: 1 }}

Diff for: src/components/dashboard/chart-section.tsx

+18-15
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,42 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
55
import { Button } from "@/components/ui/button"
66
import { TableIcon, BarChartIcon } from 'lucide-react'
77
import { OffersByCourse } from './charts/offers-by-course'
8-
import { OffersByWorkExperience } from './charts/offers-by-work-experience'
8+
import { OffersByDepartment } from './charts/offers-by-department'
99
import { OffersByGender } from './charts/offers-by-gender'
1010
import { OffersByAcademics } from './charts/offers-by-academics'
11-
import { OffersByCourseCategory } from './charts/offers-by-course-category'
11+
import { OffersByCategory } from './charts/offers-by-category'
1212
import { OffersByIndustryType } from './charts/offers-by-industry-type'
1313

14-
type ChartType = 'course' | 'workExperience' | 'gender' | 'academics' | 'courseCategory' | 'industryType'
14+
import { SeasonDataFC } from '@/helpers/analytics-dashboard/types'
15+
16+
type ChartType = 'course' | 'department' | 'gender' | 'academics' | 'category' | 'industryType'
1517
type ViewType = 'chart' | 'table'
1618

1719
interface ChartConfig {
1820
type: ChartType
1921
title: string
20-
component: React.ComponentType<{ viewType: ViewType }>
22+
component: React.ComponentType<{ viewType: ViewType, data: any }>
23+
data: any
2124
}
2225

23-
export function ChartSection() {
26+
export function ChartSection({ stats }: { stats: SeasonDataFC }) {
2427
// Individual view state for each chart
2528
const [viewTypes, setViewTypes] = useState<Record<ChartType, ViewType>>({
2629
course: 'chart',
27-
workExperience: 'chart',
30+
department: 'chart',
2831
gender: 'chart',
2932
academics: 'chart',
30-
courseCategory: 'chart',
33+
category: 'chart',
3134
industryType: 'chart'
3235
})
3336

3437
const charts: ChartConfig[] = [
35-
{ type: 'course', title: 'Offers by Course', component: OffersByCourse },
36-
{ type: 'workExperience', title: 'Offers by Work Experience', component: OffersByWorkExperience },
37-
{ type: 'gender', title: 'Offers by Gender', component: OffersByGender },
38-
{ type: 'academics', title: 'Offers by College Academics', component: OffersByAcademics },
39-
{ type: 'courseCategory', title: 'Offers by Course Category', component: OffersByCourseCategory },
40-
{ type: 'industryType', title: 'Offers by Industry Type', component: OffersByIndustryType }
38+
{ type: 'course', title: 'Offers by Courses', component: OffersByCourse, data: stats.courseWiseStats },
39+
{ type: 'department', title: 'Offers by Departments', component: OffersByDepartment, data: stats.departmentWiseStats },
40+
{ type: 'gender', title: 'Offers by Gender', component: OffersByGender, data: stats.genderWiseStats },
41+
{ type: 'academics', title: 'Offers by Academics', component: OffersByAcademics, data: stats.academicWiseStats},
42+
{ type: 'category', title: 'Offers by Category', component: OffersByCategory, data: stats.categoryWiseStats },
43+
// { type: 'industryType', title: 'Offers by Industry Type', component: OffersByIndustryType, data: stats.overallStats }
4144
]
4245

4346
// Toggle view for a specific chart
@@ -50,7 +53,7 @@ export function ChartSection() {
5053

5154
return (
5255
<div className="space-y-6">
53-
{charts.map(({ type, title, component: ChartComponent }) => (
56+
{charts.map(({ type, title, component: ChartComponent, data }) => (
5457
<Card key={type} className="w-full">
5558
<CardHeader className="flex flex-row items-center justify-between">
5659
<CardTitle>{title}</CardTitle>
@@ -67,7 +70,7 @@ export function ChartSection() {
6770
</Button>
6871
</CardHeader>
6972
<CardContent>
70-
<ChartComponent viewType={viewTypes[type]} />
73+
<ChartComponent viewType={viewTypes[type]} data={data} />
7174
</CardContent>
7275
</Card>
7376
))}

Diff for: src/components/dashboard/charts/offers-by-academics.tsx

+106-24
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,95 @@
11
'use client'
22

3-
import { Line, LineChart, CartesianGrid, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'
3+
import { Bar, BarChart, Line, LineChart, CartesianGrid, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'
44
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
55

6-
const data = [
7-
{ cgpa: '6.0-6.5', offers: 20 },
8-
{ cgpa: '6.5-7.0', offers: 40 },
9-
{ cgpa: '7.0-7.5', offers: 80 },
10-
{ cgpa: '7.5-8.0', offers: 120 },
11-
{ cgpa: '8.0-8.5', offers: 160 },
12-
{ cgpa: '8.5-9.0', offers: 80 },
13-
{ cgpa: '9.0-10.0', offers: 20 },
14-
]
15-
16-
export function OffersByAcademics({ viewType }: { viewType: 'chart' | 'table' }) {
6+
interface AcademicStats {
7+
totalRegisteredStudentsCount: number
8+
placedStudentsCount: number
9+
placementPercentage: number
10+
unplacedPercentage: number
11+
highestPackage: number
12+
lowestPackage: number
13+
meanPackage: number
14+
medianPackage: number
15+
modePackage: number
16+
totalOffers: number
17+
totalCompaniesOffering: number
18+
}
19+
20+
interface AcademicWiseStats {
21+
[key: string]: AcademicStats
22+
}
23+
24+
interface OffersByAcademicsProps {
25+
viewType: 'chart' | 'table'
26+
data: AcademicWiseStats
27+
}
28+
29+
const CustomTooltip = ({ active, payload, label }: any) => {
30+
if (active && payload && payload.length) {
31+
const stats = payload[0].payload.stats;
32+
return (
33+
<div className="bg-white p-4 rounded-lg shadow-lg border">
34+
<h3 className="font-bold">{`CPI : ${label}`}</h3>
35+
<p>Total Registered Students: {stats.totalRegisteredStudentsCount}</p>
36+
<p>Placed Students: {stats.placedStudentsCount}</p>
37+
<p>Placement %: {stats.placementPercentage.toFixed(2)}%</p>
38+
<p>Unplaced %: {stats.unplacedPercentage.toFixed(2)}%</p>
39+
<p>Highest Package: ₹{stats.highestPackage.toFixed(2)}L</p>
40+
<p>Lowest Package: ₹{stats.lowestPackage.toFixed(2)}L</p>
41+
<p>Mean Package: ₹{stats.meanPackage.toFixed(2)}L</p>
42+
<p>Median Package: ₹{stats.medianPackage.toFixed(2)}L</p>
43+
<p>Mode Package: ₹{stats.modePackage.toFixed(2)}L</p>
44+
<p>Total Offers: {stats.totalOffers}</p>
45+
<p>Total Companies Offering: {stats.totalCompaniesOffering}</p>
46+
</div>
47+
);
48+
}
49+
return null;
50+
};
51+
52+
export function OffersByAcademics({ viewType, data = {} }: OffersByAcademicsProps) {
53+
const transformedData = Object.entries(data).map(([cpi, stats]) => ({
54+
cpi,
55+
placementPercentage: stats.placementPercentage,
56+
stats
57+
}));
58+
1759
if (viewType === 'table') {
1860
return (
1961
<Table>
2062
<TableHeader>
2163
<TableRow>
22-
<TableHead>CGPA Range</TableHead>
23-
<TableHead>Offers</TableHead>
64+
<TableHead>Course</TableHead>
65+
<TableHead>Total Students</TableHead>
66+
<TableHead>Placed Students</TableHead>
67+
<TableHead>Placement %</TableHead>
68+
<TableHead>Unplaced %</TableHead>
69+
<TableHead>Highest Package (₹L)</TableHead>
70+
<TableHead>Lowest Package (₹L)</TableHead>
71+
<TableHead>Mean Package (₹L)</TableHead>
72+
<TableHead>Median Package (₹L)</TableHead>
73+
<TableHead>Mode Package (₹L)</TableHead>
74+
<TableHead>Total Offers</TableHead>
75+
<TableHead>Companies Offering</TableHead>
2476
</TableRow>
2577
</TableHeader>
2678
<TableBody>
27-
{data.map((item) => (
28-
<TableRow key={item.cgpa}>
29-
<TableCell>{item.cgpa}</TableCell>
30-
<TableCell>{item.offers}</TableCell>
79+
{Object.entries(data).map(([course, stats]) => (
80+
<TableRow key={course}>
81+
<TableCell>{course}</TableCell>
82+
<TableCell>{stats.totalRegisteredStudentsCount}</TableCell>
83+
<TableCell>{stats.placedStudentsCount}</TableCell>
84+
<TableCell>{stats.placementPercentage.toFixed(2)}%</TableCell>
85+
<TableCell>{stats.unplacedPercentage.toFixed(2)}%</TableCell>
86+
<TableCell>{stats.highestPackage.toFixed(2)}</TableCell>
87+
<TableCell>{stats.lowestPackage.toFixed(2)}</TableCell>
88+
<TableCell>{stats.meanPackage.toFixed(2)}</TableCell>
89+
<TableCell>{stats.medianPackage.toFixed(2)}</TableCell>
90+
<TableCell>{stats.modePackage.toFixed(2)}</TableCell>
91+
<TableCell>{stats.totalOffers}</TableCell>
92+
<TableCell>{stats.totalCompaniesOffering}</TableCell>
3193
</TableRow>
3294
))}
3395
</TableBody>
@@ -36,13 +98,33 @@ export function OffersByAcademics({ viewType }: { viewType: 'chart' | 'table' })
3698
}
3799

38100
return (
39-
<ResponsiveContainer width="100%" height={300}>
40-
<LineChart data={data}>
101+
<ResponsiveContainer width="100%" height={400}>
102+
<LineChart
103+
data={transformedData}
104+
>
41105
<CartesianGrid strokeDasharray="3 3" />
42-
<XAxis dataKey="cgpa" />
43-
<YAxis />
44-
<Tooltip />
45-
<Line type="monotone" dataKey="offers" stroke="#8884d8" />
106+
<XAxis
107+
dataKey="cpi"
108+
interval={0}
109+
angle={-45}
110+
textAnchor="end"
111+
height={100}
112+
tick={{
113+
dx: -8,
114+
dy: 10,
115+
fontSize: 12
116+
}}
117+
/>
118+
<YAxis
119+
label={{
120+
value: 'Placement %',
121+
angle: -90,
122+
position: 'insideLeft',
123+
style: { textAnchor: 'middle' }
124+
}}
125+
/>
126+
<Tooltip content={<CustomTooltip />} />
127+
<Line type="monotone" dataKey="placementPercentage" stroke="#8884d8" />
46128
</LineChart>
47129
</ResponsiveContainer>
48130
)
+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
'use client'
2+
3+
import { Bar, BarChart, CartesianGrid, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'
4+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
5+
6+
interface CategoryStats {
7+
totalRegisteredStudentsCount: number
8+
placedStudentsCount: number
9+
placementPercentage: number
10+
unplacedPercentage: number
11+
highestPackage: number
12+
lowestPackage: number
13+
meanPackage: number
14+
medianPackage: number
15+
modePackage: number
16+
totalOffers: number
17+
totalCompaniesOffering: number
18+
}
19+
20+
interface CategoryWiseStats {
21+
[key: string]: CategoryStats
22+
}
23+
24+
interface OffersByCategoryProps {
25+
viewType: 'chart' | 'table'
26+
data: CategoryWiseStats
27+
}
28+
29+
const CustomTooltip = ({ active, payload, label }: any) => {
30+
if (active && payload && payload.length) {
31+
const stats = payload[0].payload.stats;
32+
return (
33+
<div className="bg-white p-4 rounded-lg shadow-lg border">
34+
<h3 className="font-bold">{label}</h3>
35+
<p>Total Registered Students: {stats.totalRegisteredStudentsCount}</p>
36+
<p>Placed Students: {stats.placedStudentsCount}</p>
37+
<p>Placement %: {stats.placementPercentage.toFixed(2)}%</p>
38+
<p>Unplaced %: {stats.unplacedPercentage.toFixed(2)}%</p>
39+
<p>Highest Package: ₹{stats.highestPackage.toFixed(2)}L</p>
40+
<p>Lowest Package: ₹{stats.lowestPackage.toFixed(2)}L</p>
41+
<p>Mean Package: ₹{stats.meanPackage.toFixed(2)}L</p>
42+
<p>Median Package: ₹{stats.medianPackage.toFixed(2)}L</p>
43+
<p>Mode Package: ₹{stats.modePackage.toFixed(2)}L</p>
44+
<p>Total Offers: {stats.totalOffers}</p>
45+
<p>Total Companies Offering: {stats.totalCompaniesOffering}</p>
46+
</div>
47+
);
48+
}
49+
return null;
50+
};
51+
52+
export function OffersByCategory({ viewType, data = {} }: OffersByCategoryProps) {
53+
const transformedData = Object.entries(data).map(([category, stats]) => ({
54+
category,
55+
placementPercentage: stats.placementPercentage,
56+
stats
57+
}));
58+
59+
if (viewType === 'table') {
60+
return (
61+
<Table>
62+
<TableHeader>
63+
<TableRow>
64+
<TableHead>Course</TableHead>
65+
<TableHead>Total Students</TableHead>
66+
<TableHead>Placed Students</TableHead>
67+
<TableHead>Placement %</TableHead>
68+
<TableHead>Unplaced %</TableHead>
69+
<TableHead>Highest Package (₹L)</TableHead>
70+
<TableHead>Lowest Package (₹L)</TableHead>
71+
<TableHead>Mean Package (₹L)</TableHead>
72+
<TableHead>Median Package (₹L)</TableHead>
73+
<TableHead>Mode Package (₹L)</TableHead>
74+
<TableHead>Total Offers</TableHead>
75+
<TableHead>Companies Offering</TableHead>
76+
</TableRow>
77+
</TableHeader>
78+
<TableBody>
79+
{Object.entries(data).map(([course, stats]) => (
80+
<TableRow key={course}>
81+
<TableCell>{course}</TableCell>
82+
<TableCell>{stats.totalRegisteredStudentsCount}</TableCell>
83+
<TableCell>{stats.placedStudentsCount}</TableCell>
84+
<TableCell>{stats.placementPercentage.toFixed(2)}%</TableCell>
85+
<TableCell>{stats.unplacedPercentage.toFixed(2)}%</TableCell>
86+
<TableCell>{stats.highestPackage.toFixed(2)}</TableCell>
87+
<TableCell>{stats.lowestPackage.toFixed(2)}</TableCell>
88+
<TableCell>{stats.meanPackage.toFixed(2)}</TableCell>
89+
<TableCell>{stats.medianPackage.toFixed(2)}</TableCell>
90+
<TableCell>{stats.modePackage.toFixed(2)}</TableCell>
91+
<TableCell>{stats.totalOffers}</TableCell>
92+
<TableCell>{stats.totalCompaniesOffering}</TableCell>
93+
</TableRow>
94+
))}
95+
</TableBody>
96+
</Table>
97+
)
98+
}
99+
100+
return (
101+
<ResponsiveContainer width="100%" height={400}>
102+
<BarChart
103+
data={transformedData}
104+
margin={{
105+
top: 20,
106+
right: 30,
107+
left: 20,
108+
}}
109+
>
110+
<CartesianGrid strokeDasharray="3 3" />
111+
<XAxis
112+
dataKey="category"
113+
interval={0}
114+
angle={-45}
115+
textAnchor="end"
116+
height={100}
117+
tick={{
118+
dx: -8,
119+
dy: 10,
120+
fontSize: 12
121+
}}
122+
/>
123+
<YAxis
124+
label={{
125+
value: 'Placement %',
126+
angle: -90,
127+
position: 'insideLeft',
128+
style: { textAnchor: 'middle' }
129+
}}
130+
/>
131+
<Tooltip content={<CustomTooltip />} />
132+
<Bar
133+
dataKey="placementPercentage"
134+
fill="#82ca9d"
135+
name="Placement %"
136+
/>
137+
</BarChart>
138+
</ResponsiveContainer>
139+
)
140+
}
141+

0 commit comments

Comments
 (0)