Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions components/DetailsCSS.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,15 @@
.detailsBlockTitle {
font-size: 1;
}

.no_certification {
display: flex;
font-size: 1.2rem;
font-weight: bold;

Check warning on line 67 in components/DetailsCSS.module.css

View check run for this annotation

codefactor.io / CodeFactor

components/DetailsCSS.module.css#L67

Unexpected duplicate "font-weight". (declaration-block-no-duplicate-properties)
margin: 10px;
border-bottom: 2px solid black;
color: black;
font-weight: 100;
background-color: #f1be32;
max-width: fit-content;
}
23 changes: 20 additions & 3 deletions components/DetailsDashboard.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React from 'react';
import styles from './DetailsCSS.module.css';
import DetailsDashboardList from './DetailsDashboardList';
//getStudentProgressInSuperblock

import { getStudentProgressInSuperblock } from '../util/api_proccesor';
import {
getStudentProgressInSuperblock,
extractFilteredCompletionTimestamps
} from '../util/api_proccesor';
import StudentActivityChart from './StudentActivityChart';

export default function DetailsDashboard(props) {
const printSuperblockTitle = individualSuperblockJSON => {
Expand All @@ -23,8 +25,23 @@ export default function DetailsDashboard(props) {
);
};

const selectedSuperblocks = props.superblocksDetailsJSONArray.map(
arrayOfBlockObjs => arrayOfBlockObjs[0].superblock
);
const filteredCompletionTimestamps = extractFilteredCompletionTimestamps(
props.studentData.certifications,
selectedSuperblocks
);

return (
<>
{selectedSuperblocks.length > 0 ? (
<StudentActivityChart timestamps={filteredCompletionTimestamps} />
) : (
<p className={styles.no_certification}>
No certifications selected for this class.
</p>
)}
{props.superblocksDetailsJSONArray.map((arrayOfBlockObjs, idx) => {
let index = props.superblocksDetailsJSONArray.indexOf(arrayOfBlockObjs);
let superblockDashedName =
Expand Down
156 changes: 156 additions & 0 deletions components/StudentActivityChart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import React, { useEffect, useState } from 'react';
import styles from './StudentActivityChart.module.css';

const daysOfWeek = ['Mon', 'Wed', 'Fri'];

const generateActivityData = timestamps => {
const oneYearAgo = new Date();
oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
const activityData = {};
timestamps.forEach(timestamp => {
const date = new Date(timestamp);
if (date >= oneYearAgo) {
const key = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(
2,
'0'
)}-${String(date.getDate()).padStart(2, '0')}`;
activityData[key] = (activityData[key] || 0) + 1;
}
});
return activityData;
};

const activityLevels = ['#3b3b4f', '#99c9ff'];

const getColor = count => {
if (count > 0) return activityLevels[1];
return activityLevels[0];
};

const getPreviousYearDate = date => {
const previousYearDate = new Date(date);
previousYearDate.setFullYear(date.getFullYear() - 1);
return previousYearDate;
};

const StudentActivityChart = ({ timestamps }) => {
const [weeks, setWeeks] = useState([]);
const [activityData, setActivityData] = useState({});

useEffect(() => {
const Data = generateActivityData(timestamps);
setActivityData(Data);
}, [timestamps]);

useEffect(() => {
const today = new Date();
// today.setDate(today.getDate() - 5); // For testing purposes
const startDate = getPreviousYearDate(today);

// Increment today to the next day to include today's activity
today.setDate(today.getDate() + 1);

const weeks = [];
let firstWeek = [];
const startDay = startDate.getDay();

// Fill the first week with the correct dates
for (let i = 0; i < startDay; i++) {
firstWeek.push({ date: null, count: 0 });
}

let chart_cutoff = false;
for (let i = 0; i < 54; i++) {
const week = i === 0 ? firstWeek : [];
if (chart_cutoff) {
break;
}
for (let j = week.length; j < 7; j++) {
const date = new Date(startDate);
date.setDate(startDate.getDate() + i * 7 + j - startDay);
if (date.toDateString() === today.toDateString()) {
chart_cutoff = true;
break;
}
const key = `${date.getFullYear()}-${String(
date.getMonth() + 1
).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
week.push({ date, count: activityData[key] || 0 });
}
weeks.push(week);
}

setWeeks(weeks);
}, [activityData]);

return (
<div className={styles.parentContainer}>
<div className={styles.chartContainer}>
<div className={styles.chartHeader}>
<h3 className={styles.contributionsTotal}>
{Object.keys(activityData).length} contributions in the last year
</h3>
</div>
<div className={styles.monthLabels}>
{weeks.map((week, index) => {
const firstDay = week[0]?.date;
if (firstDay && firstDay.getDate() <= 8 && firstDay.getDate() > 1) {
return (
<div key={index} className={styles.monthLabel}>
{firstDay.toLocaleString('default', { month: 'short' })}
</div>
);
}
return <div key={index} className={styles.monthLabel}></div>;
})}
</div>
<div className={styles.chart}>
<div className={styles.dayLabels}>
{daysOfWeek.map((day, index) => (
<div key={index} className={styles.dayLabel}>
{day}
</div>
))}
</div>

<div className={styles.chartWithLegend}>
<div className={styles.grid}>
{weeks.map((week, index) => (
<div key={index} className={styles.week}>
{week.map((day, index) =>
day.date ? (
<div
key={index}
className={styles.day}
style={{ backgroundColor: getColor(day.count) }}
title={`${day.date.toDateString()}: ${
day.count
} completions`}
></div>
) : (
<div key={index} className={styles.dayEmpty}></div>
)
)}
</div>
))}
</div>
<div className={styles.legend}>
<span>Inactive</span>
<div
className={styles.legendColor}
style={{ backgroundColor: activityLevels[0] }}
></div>
<span>Active</span>
<div
className={styles.legendColor}
style={{ backgroundColor: activityLevels[1] }}
></div>
</div>
</div>
</div>
</div>
</div>
);
};

export default StudentActivityChart;
113 changes: 113 additions & 0 deletions components/StudentActivityChart.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
.parentContainer {
display: flex;
justify-content: center;
align-items: center;
}

.chartContainer {
border: 1px solid #444;
border-radius: 8px;
padding: 15px;
display: inline-block;
margin: 10px;
color: #f5f6f7;
background-color: #1b1b32;
font-family: 'Roboto Mono', monospace;
}

.chartHeader {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
color: #f5f6f7;
}

.monthLabels {
display: grid;
grid-template-columns: repeat(54, 20px);
gap: 2px;
font-size: 10px;
font-family: 'Roboto Mono', monospace;
color: #f5f6f7;
margin-bottom: 5px;
margin-left: 6px;
}

.monthLabel {
text-align: center;
width: 20px;
}

.chart {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: center;
}

.dayLabels {
display: flex;
flex-direction: column;
margin-right: 5px;
margin-top: 25px;
font-size: 10px;
color: #f5f6f7;
}

.dayLabel {
height: 44px;
text-align: right;
padding-right: 5px;
}

.grid {
display: grid;
grid-template-columns: repeat(54, 20px);
gap: 2px;
}

.week {
display: grid;
grid-template-rows: repeat(7, 20px);
gap: 2px;
}

.day {
width: 20px;
height: 20px;
border-radius: 3px;
border: 1px solid #0a0a23;
}

.contributionsTotal {
text-align: left;
font-size: 14px;
margin-bottom: 10px;
font-weight: bold;
}

.chartWithLegend {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}

.legend {
display: flex;
align-items: right;
justify-content: right;
width: 100%;
margin-top: 10px;
font-size: 10px;
color: #f5f6f7;
}

.legendColor {
width: 12px;
height: 12px;
margin: 0 2px;
border-radius: 3px;
border: 1px solid #ddd;
}
16 changes: 8 additions & 8 deletions mock-json-server/fccdata.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
{
"id": "5f33071498eb2472b87ddee4",
"challengeName": "Step 1",
"completedDate": 1475094716730,
"completedDate": 1709164800000,
"files": []
},
{
"id": "5f3313e74582ad9d063e3a38",
"challengeName": "Step 2",
"completedDate": 1537207306322,
"completedDate": 1722384000000,
"files": []
}
]
Expand All @@ -36,13 +36,13 @@
{
"id": "5895f700f9fc0f352b528e63",
"challengeName": "Set up a Template Engine",
"completedDate": 98448684,
"completedDate": 1714435200000,
"files": []
},
{
"id": "5895f70df9fc0f352b528e6a",
"challengeName": "Create New Middleware",
"completedDate": 98448643284,
"completedDate": 1727654400000,
"files": []
}
]
Expand All @@ -54,7 +54,7 @@
{
"id": "587d824a367417b2b2512c46",
"challengeName": "Learn How JavaScript Assertions Work",
"completedDate": 47664591,
"completedDate": 1704067200000,
"files": []
}
]
Expand All @@ -77,13 +77,13 @@
{
"id": "5895f700f9fc0f352b528e63",
"challengeName": "Set up a Template Engine",
"completedDate": 98448684,
"completedDate": 1711843200000,
"files": []
},
{
"id": "5895f70df9fc0f352b528e6a",
"challengeName": "Create New Middleware",
"completedDate": 98448643284,
"completedDate": 1725062400000,
"files": []
}
]
Expand All @@ -95,7 +95,7 @@
{
"id": "587d824a367417b2b2512c46",
"challengeName": "Learn How JavaScript Assertions Work",
"completedDate": 47664591,
"completedDate": 1732924800000,
"files": []
}
]
Expand Down
Loading