Skip to content

Commit

Permalink
Merge pull request #37 from erland-syafiq/applicants-dashboard
Browse files Browse the repository at this point in the history
Applicants dashboard
  • Loading branch information
erland-syafiq authored Jun 16, 2024
2 parents e69105f + dd31548 commit 99a4124
Show file tree
Hide file tree
Showing 7 changed files with 303 additions and 10 deletions.
20 changes: 10 additions & 10 deletions site/app/api/applicants/route.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { generateRandomId, getCurrentDate } from "@/app/utils/util";
import { getApplicants, putApplicant } from "../db/dynamodb";

// export async function GET() {
// try {
// const applicants = await getApplicants();
// return Response.json(applicants);
// }
// catch (e) {
// console.log(e);
// return new Response("Error with applicants", 500);
// }
// }
export async function GET() {
try {
const applicants = await getApplicants();
return Response.json(applicants);
}
catch (e) {
console.log(e);
return new Response("Error with applicants", 500);
}
}

export async function POST(request) {
try {
Expand Down
135 changes: 135 additions & 0 deletions site/app/applicants/Dashboard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@

"use client"

import React from "react";
import dynamic from "next/dynamic";
import "chart.js/auto";
import { Doughnut } from "react-chartjs-2";
import 'chartjs-adapter-date-fns';
import { invoiceStatusCategories } from "@/app/utils/applicantUtils";

const Line = dynamic(() => import("react-chartjs-2").then((mod) => mod.Line), {
ssr: false
});



/**
* Dashboard Component
* @param {Object[]} applicants array of applicants in descending order
* @returns {JSX.ELement} a dashboard with metrics on total number of participants and more
*/
export default function Dashboard({applicants}) {

// Find total number of participants
const totalParticipants = applicants.reduce((accumulator, applicant) => accumulator + applicant.delegationSize, 0);

// Find number of days since last sign up.
const daysSinceSignUp = Math.floor((new Date() - new Date(applicants[0].date)) / (24 * 60 * 60 * 1000));

// Comppile data for total participants over time
let sum = totalParticipants;
let totalParticipantsDates = [];
let totalParticipantsOverTime = [];
let pastDate = "2022-01-01"
for (const applicant of applicants) {
if (pastDate != applicant.date) {
totalParticipantsOverTime.push(sum);
totalParticipantsDates.push(applicant.date);
}
else {
totalParticipantsOverTime[totalParticipantsOverTime.length - 1] = sum;
}

sum -= applicant.delegationSize;
pastDate = applicant.date;
}

totalParticipantsOverTime.reverse();
totalParticipantsDates.reverse();


// Compile data for invoice statuses
const invoiceStatusData = new Array(3).fill(0);
applicants.forEach((applicant) => {
invoiceStatusData[applicant.invoiceStatus] += applicant.delegationSize;
});


return (
<section className="Analysis">
<div className="row">
<div className="col-3">
<div className="card text-center">
<h4>Total Participants</h4>
<h2>{totalParticipants}</h2>
</div>
<div className="card text-center">
<h4>Number of Days Since a Sign Up</h4>
<h2>{daysSinceSignUp}</h2>
</div>
</div>
<div className="col-6 text-center">
<div className="card">
<Line
data={{
labels: totalParticipantsDates,
datasets: [{
label: 'Total Number of Participants',
data: totalParticipantsOverTime,
borderWidth: 1,
borderColor: '#630031',
pointBackgroundColor: '#630031',
pointBorderWidth: 2,
borderSize: 2,
}]
}}
options={{
scales: {
x: {
type: 'time',
time: {
unit: 'day',
displayFormats: {
day: 'yyyy-MM-dd'
},
distribution: 'linear',
},
title: {
display: true,
text: 'Signup Date'
},
},
y: {
beginAtZero: true,
}
}
}}
/>
</div>
<h4>
Total Number of Delegates
</h4>
</div>
<div className="col-3">
<div className="card text-center">
<h4>Applicants' Invoice Status</h4>
<Doughnut
data={{
labels: invoiceStatusCategories,
datasets: [{
data: invoiceStatusData,
backgroundColor: ['#DC3545', '#FFC107', '#28A745'],
borderWidth: 0
}]
}}
options={{
cutout: "60%"
}}
/>
</div>
</div>
</div>
</section>
);
};
17 changes: 17 additions & 0 deletions site/app/applicants/DashboardPage.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.card {
border: solid var(--primary);
background: var(--primary-background);
box-shadow: -5px 5px 0px 0px var(--tertiary);
padding: 40px;
margin-bottom: 40px;
margin-right: 10px;
margin-left: 10px;
}

.card > h2 {
color: var(--primary);
}

.dashboard {
background: var(--secondary-background);
}
74 changes: 74 additions & 0 deletions site/app/applicants/page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React from "react";
import { getApplicants } from "@/app/api/db/dynamodb";
import Dashboard from "./Dashboard";
import Link from "next/link";
import "./DashboardPage.css";
import { invoiceStatusToString } from "@/app/utils/applicantUtils";

/**
* Page displays some metrics on our current applicants including total number of participants,
* total number of participants over time, our current invoice status demographics, and more. It
* also displays a table of all of the participants.
*
* @returns {JSX.Element} dashboard page
*/
export default async function DashboardPage() {
try {
const applicants = await getApplicants();
// Sorts applicants in descending order by date in place
applicants.sort((a, b) => new Date(b.date) - new Date(a.date));

return (
<main className="dashboard">
<div className="container">
<h2>Delegations</h2>
<Dashboard applicants={applicants}/>
<div className="table-responsive">
<table className="table table-sm table-striped">
<thead>
<tr>
<th>Date</th>
<th>School</th>
<th>Advisor Name</th>
<th>Advisor Email</th>
<th>Delegation Size</th>
<th>Invoice Status</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{
applicants.map((applicant) => {
return (
<tr key={applicant.id} >
<td>{applicant.date}</td>
<td>{applicant.schoolName}</td>
<td>{applicant.advisorName}</td>
<td>{applicant.advisorEmail}</td>
<td>{applicant.delegationSize}</td>
<td>{invoiceStatusToString(applicant.invoiceStatus)}</td>
<td>
<Link href={`/applicants/edit/${applicant.id}`}>Edit</Link> | <Link href={`/applicants/details/${applicant.id}`}>Details</Link> | <Link href={`/applicants/edit/${applicant.id}`}>Delete</Link>
</td>
</tr>
)
})
}
</tbody>
</table>

</div>
</div>
</main>
)

}
catch (e) {
return (
<main className="container">
<h1>Error: {e.name}</h1>
<p>{e.message}</p>
</main>
)
}
}
17 changes: 17 additions & 0 deletions site/app/utils/applicantUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

/**
* Different categories of invoices
*/
export const invoiceStatusCategories = ['Invoice Not Sent', 'Payment Not Received', 'Payment Received'];

/**
* Takes an invoice integer and returns its corresponding category name
* @param {number} number
* @returns {string} Invoice category name
*/
export function invoiceStatusToString(number) {
if (number >= invoiceStatusCategories.length) {
return "Invalid Invoice Status";
}
return invoiceStatusCategories[number];
}
47 changes: 47 additions & 0 deletions site/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
},
"dependencies": {
"aws-sdk": "^2.1633.0",
"chart.js": "^4.4.3",
"chartjs-adapter-date-fns": "^3.0.0",
"next": "14.2.3",
"react": "^18",
"react-chartjs-2": "^5.2.0",
"react-dom": "^18"
},
"devDependencies": {
Expand Down

0 comments on commit 99a4124

Please sign in to comment.