-
-
-
- {Math.round(rating * 100) / 100} / 5
+
+
+
+ {Math.round(averageRating * 100) / 100} / 5
diff --git a/client/src/lib/utils.ts b/client/src/lib/utils.ts
index 9d762478..686a4389 100644
--- a/client/src/lib/utils.ts
+++ b/client/src/lib/utils.ts
@@ -1,6 +1,7 @@
import { Course } from '../model/Course';
import { Instructor } from '../model/Instructor';
import { Schedule } from '../model/Schedule';
+import { Review } from '../model/Review';
export const uniqueTermInstructors = (course: Course) => {
const termInstructors = course.instructors.filter((i) =>
@@ -72,3 +73,23 @@ export const sortSchedulesByBlocks = (schedules: Schedule[]) => {
export const getUrl = (): string => {
return import.meta.env.VITE_API_URL ?? '';
};
+
+
+export const countRatings = (
+ type: 'rating' | 'difficulty',
+ reviews: Review[]
+) => {
+ const ratings: number[] = [0, 0, 0, 0, 0];
+ const target = (r: Review) => {
+ switch (type) {
+ case 'rating':
+ return r.rating;
+ case 'difficulty':
+ return r.difficulty;
+ }
+ };
+
+ reviews.forEach((r: Review) => ratings[target(r) - 1]++);
+
+ return ratings;
+};
diff --git a/client/src/pages/CoursePage.tsx b/client/src/pages/CoursePage.tsx
index 4c42fa38..19f3f6a7 100644
--- a/client/src/pages/CoursePage.tsx
+++ b/client/src/pages/CoursePage.tsx
@@ -32,7 +32,7 @@ export const CoursePage = () => {
const [addReviewOpen, setAddReviewOpen] = useState(false);
const [alertMessage, setAlertMessage] = useState('');
const [alertStatus, setAlertStatus] = useState
(null);
- const [allReviews, setAllReviews] = useState(undefined);
+ const [allReviews, setAllReviews] = useState([]);
const [course, setCourse] = useState();
const [editReviewOpen, setEditReviewOpen] = useState(false);
const [key, setKey] = useState(0);
@@ -122,21 +122,12 @@ export const CoursePage = () => {
localStorage.removeItem(course._id);
};
- const userReview = allReviews?.find((r) => r.userId === user?.id);
-
- const averageRating =
- _.sumBy(allReviews, (r) => r.rating) / (allReviews ?? []).length;
-
- const averageDifficulty =
- _.sumBy(allReviews, (r) => r.difficulty) / (allReviews ?? []).length;
-
+ const userReview = allReviews.find((r) => r.userId === user?.id);
return (
diff --git a/client/src/pages/Instructor.tsx b/client/src/pages/Instructor.tsx
index e78bcb0d..fb76d6e7 100644
--- a/client/src/pages/Instructor.tsx
+++ b/client/src/pages/Instructor.tsx
@@ -1,5 +1,8 @@
import _ from 'lodash';
import { Fragment, useEffect, useState } from 'react';
+import { HiChartBar, HiChartPie } from 'react-icons/hi';
+import { countRatings } from '../lib/utils';
+import { Instructor as InstructorType } from '../model/Instructor';
import { Link, useParams } from 'react-router-dom';
import { CourseReview } from '../components/CourseReview';
@@ -7,7 +10,6 @@ import { Layout } from '../components/Layout';
import { RatingInfo } from '../components/RatingInfo';
import { useAuth } from '../hooks/useAuth';
import { fetchClient } from '../lib/fetchClient';
-import { Instructor as InstructorType } from '../model/Instructor';
import { Review } from '../model/Review';
import { Loading } from './Loading';
import { NotFound } from './NotFound';
@@ -18,6 +20,7 @@ export const Instructor = () => {
const [reviews, setReviews] = useState([]);
const [showAllReviews, setShowAllReviews] = useState(false);
+ const [chartType, setChartType] = useState<'pie' | 'histogram'>('pie');
const [instructor, setInstructor] = useState<
InstructorType | undefined | null
@@ -41,12 +44,12 @@ export const Instructor = () => {
if (instructor === undefined) return ;
if (instructor === null) return ;
+ const ratingMap = countRatings('rating', reviews);
+ const difficultyMap = countRatings('difficulty', reviews);
+
const userReview = reviews.find((r) => r.userId === user?.id);
const uniqueReviews = _.uniqBy(reviews, (r) => r.courseId);
- const averageRating = _.sumBy(reviews, (r) => r.rating) / reviews.length;
- const averageDifficulty =
- _.sumBy(reviews, (r) => r.difficulty) / reviews.length;
return (
@@ -59,13 +62,41 @@ export const Instructor = () => {
{params.name && decodeURIComponent(params.name)}
-
- {uniqueReviews.length ? (
- <>
-
-
- >
- ) : null}
+
+
+
+
+
+ setChartType('pie')}
+ size={30}
+ />
+ setChartType('histogram')}
+ size={30}
+ />
{uniqueReviews.length ? (
@@ -85,13 +116,43 @@ export const Instructor = () => {
)}
-
- {uniqueReviews.length ? (
- <>
-
-
- >
- ) : null}
+
+
+
+
+
+
+ setChartType('pie')}
+ size={30}
+ />
+ setChartType('histogram')}
+ size={30}
+ />
+
diff --git a/package-lock.json b/package-lock.json
index 70fd8c39..c0df1566 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,11 +11,13 @@
"@fontsource/inter": "^4.5.15",
"@headlessui/react": "^1.7.13",
"@heroicons/react": "^2.0.16",
+ "chart.js": "^4.3.0",
"date-fns": "^2.29.3",
"formik": "^2.2.9",
"formik-persist-values": "^1.4.1",
"lodash": "^4.17.21",
"react": "^18.2.0",
+ "react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0",
"react-feather": "^2.0.10",
"react-icons": "^4.8.0",
@@ -976,6 +978,11 @@
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
"dev": true
},
+ "node_modules/@kurkle/color": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmmirror.com/@kurkle/color/-/color-0.3.2.tgz",
+ "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw=="
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1872,6 +1879,17 @@
"node": ">=4"
}
},
+ "node_modules/chart.js": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmmirror.com/chart.js/-/chart.js-4.3.0.tgz",
+ "integrity": "sha512-ynG0E79xGfMaV2xAHdbhwiPLczxnNNnasrmPEXriXsPJGjmhOBYzFVEsB65w2qMDz+CaBJJuJD0inE/ab/h36g==",
+ "dependencies": {
+ "@kurkle/color": "^0.3.0"
+ },
+ "engines": {
+ "pnpm": ">=7"
+ }
+ },
"node_modules/check-more-types": {
"version": "2.24.0",
"resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz",
@@ -5480,6 +5498,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-chartjs-2": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmmirror.com/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz",
+ "integrity": "sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==",
+ "peerDependencies": {
+ "chart.js": "^4.1.1",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
diff --git a/package.json b/package.json
index 77250654..4af221e4 100644
--- a/package.json
+++ b/package.json
@@ -21,11 +21,13 @@
"@fontsource/inter": "^4.5.15",
"@headlessui/react": "^1.7.13",
"@heroicons/react": "^2.0.16",
+ "chart.js": "^4.3.0",
"date-fns": "^2.29.3",
"formik": "^2.2.9",
"formik-persist-values": "^1.4.1",
"lodash": "^4.17.21",
"react": "^18.2.0",
+ "react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0",
"react-feather": "^2.0.10",
"react-icons": "^4.8.0",