From 916c99ead57e59e29a1a9b6b48bc3c38a704e726 Mon Sep 17 00:00:00 2001 From: Sami Al Saati Date: Wed, 16 Apr 2025 15:30:38 +0200 Subject: [PATCH] added avg rating. fixed some bugs --- my-app/package.json | 3 +- my-app/src/dev/README.md | 70 ++++++++++ my-app/src/dev/index.js | 9 ++ my-app/src/dev/palette.jsx | 25 ++++ my-app/src/dev/previews.jsx | 11 ++ my-app/src/dev/useInitial.js | 15 ++ my-app/src/model.js | 6 + my-app/src/presenters/ListViewPresenter.jsx | 16 +-- .../src/views/Components/CoursePagePopup.jsx | 129 ++++++++++++------ .../CourseViewComponents/ModalComponent.jsx | 32 +---- my-app/src/views/Components/StarComponent.jsx | 4 +- my-app/src/views/ReviewView.jsx | 1 + 12 files changed, 241 insertions(+), 80 deletions(-) create mode 100644 my-app/src/dev/README.md create mode 100644 my-app/src/dev/index.js create mode 100644 my-app/src/dev/palette.jsx create mode 100644 my-app/src/dev/previews.jsx create mode 100644 my-app/src/dev/useInitial.js diff --git a/my-app/package.json b/my-app/package.json index b4dfbb4c..2c0ed709 100644 --- a/my-app/package.json +++ b/my-app/package.json @@ -28,7 +28,8 @@ "react-infinite-scroll-component": "^6.1.0", "react-router-dom": "^7.4.0", "reactflow": "^11.11.4", - "tailwindcss": "^4.0.17" + "tailwindcss": "^4.0.17", + "@react-buddy/ide-toolbox": "^2.4.0" }, "devDependencies": { "@eslint/js": "^9.21.0", diff --git a/my-app/src/dev/README.md b/my-app/src/dev/README.md new file mode 100644 index 00000000..1f5e5e75 --- /dev/null +++ b/my-app/src/dev/README.md @@ -0,0 +1,70 @@ +This directory contains utility files which enable some visual features of the +[React Buddy](https://plugins.jetbrains.com/plugin/17467-react-buddy/) plugin. +Files in the directory should be committed to source control. + +React Buddy palettes describe reusable components and building blocks. `React Palette` tool window becomes available +when an editor with React components is active. You can drag and drop items from the tool window to the code editor or +JSX Outline. Alternatively, you can insert components from the palette using code generation action (`alt+insert` / +`⌘ N`). + +Add components to the palette using `Add to React Palette` intention or via palette editor (look for the corresponding +link in `palette.tsx`). There are some ready-to-use palettes for popular React libraries which are published as npm +packages and can be added as a dependency: + +```jsx +import AntdPalette from "@react-buddy/palette-antd"; +import ReactIntlPalette from "@react-buddy/palette-react-intl"; + +export const PaletteTree = () => ( + + + + + + + + + Card content + + + + + + + + + +) +``` + +React Buddy explicitly registers any previewed component in the `previews.tsx` file so that you can specify required +props. + +```jsx + + + +``` + +You can add some global initialization logic for the preview mode in `useInitital.ts`, +e.g. implicitly obtain user session: + +```typescript +export const useInitial: () => InitialHookStatus = () => { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(false); + + useEffect(() => { + setLoading(true); + async function login() { + const response = await loginRequest(DEV_LOGIN, DEV_PASSWORD); + if (response?.status !== 200) { + setError(true); + } + setLoading(false); + } + login(); + }, []); + return { loading, error }; +}; +``` \ No newline at end of file diff --git a/my-app/src/dev/index.js b/my-app/src/dev/index.js new file mode 100644 index 00000000..d703b95f --- /dev/null +++ b/my-app/src/dev/index.js @@ -0,0 +1,9 @@ +import React from "react" +import {useInitial} from "./useInitial" + +const ComponentPreviews = React.lazy(() => import("./previews")) + +export { + ComponentPreviews, + useInitial +} \ No newline at end of file diff --git a/my-app/src/dev/palette.jsx b/my-app/src/dev/palette.jsx new file mode 100644 index 00000000..42d9515a --- /dev/null +++ b/my-app/src/dev/palette.jsx @@ -0,0 +1,25 @@ +import {Fragment} from "react" +import { + Category, + Component, + Variant, + Palette, +} from "@react-buddy/ide-toolbox" + +export const PaletteTree = () => ( + + + + + + + + + +) + +export function ExampleLoaderComponent() { + return ( + Loading... + ) +} \ No newline at end of file diff --git a/my-app/src/dev/previews.jsx b/my-app/src/dev/previews.jsx new file mode 100644 index 00000000..d1b4a458 --- /dev/null +++ b/my-app/src/dev/previews.jsx @@ -0,0 +1,11 @@ +import {Previews} from '@react-buddy/ide-toolbox' +import {PaletteTree} from './palette' + +const ComponentPreviews = () => { + return ( + }> + + ) +} + +export default ComponentPreviews \ No newline at end of file diff --git a/my-app/src/dev/useInitial.js b/my-app/src/dev/useInitial.js new file mode 100644 index 00000000..203f394a --- /dev/null +++ b/my-app/src/dev/useInitial.js @@ -0,0 +1,15 @@ +import {useState} from 'react' + +export const useInitial = () => { + const [status, setStatus] = useState({ + loading: false, + error: false + }) + /* + Implement hook functionality here. + If you need to execute async operation, set loading to true and when it's over, set loading to false. + If you caught some errors, set error status to true. + Initial hook is considered to be successfully completed if it will return {loading: false, error: false}. + */ + return status +} diff --git a/my-app/src/model.js b/my-app/src/model.js index 533fa15b..a2929757 100644 --- a/my-app/src/model.js +++ b/my-app/src/model.js @@ -162,6 +162,12 @@ export const model = { // this.filterOptions.applyDepartmentFilter = departmentFilterState; // }, + async getAverageRating(courseCode) { + const reviews = await getReviewsForCourse(courseCode); + if (!reviews || reviews.length === 0) return null; + const total = reviews.reduce((sum, review) => sum + (review.overallRating || 0), 0); + return (total / reviews.length).toFixed(1); + }, }; diff --git a/my-app/src/presenters/ListViewPresenter.jsx b/my-app/src/presenters/ListViewPresenter.jsx index a3cbb87e..caa625a4 100644 --- a/my-app/src/presenters/ListViewPresenter.jsx +++ b/my-app/src/presenters/ListViewPresenter.jsx @@ -16,12 +16,12 @@ const ListViewPresenter = observer(({ model }) => { const container = scrollContainerRef.current; if (!container || !model.scrollPosition) return; - + const attemptScroll = () => { // refresh on significant change (same as in firebase) - if (Math.abs(container.scrollTop - model.scrollPosition) < 100) + if (Math.abs(container.scrollTop - model.scrollPosition) < 100) return; attempts++; @@ -43,7 +43,7 @@ const ListViewPresenter = observer(({ model }) => { useEffect(() => { // Load initial scroll position - const savedPosition = model.user + const savedPosition = model.user ? model.scrollPosition : localStorage.getItem("scrollPosition"); if (savedPosition) { @@ -59,7 +59,7 @@ const ListViewPresenter = observer(({ model }) => { const addFavourite = (course) => { model.addFavourite(course); } - const removeFavourite = (course) => { + const removeFavourite = (course) => { model.removeFavourite(course); } const handleFavouriteClick = (course) => { @@ -82,16 +82,16 @@ const ListViewPresenter = observer(({ model }) => { handleFavouriteClick={handleFavouriteClick} isOpen={isPopupOpen} onClose={() => setIsPopupOpen(false)} course={selectedCourse} - prerequisiteTree={preP} + prerequisiteTree={preP} reviewPresenter={reviewPresenter}/> - + return { setIsPopupOpen={setIsPopupOpen} setSelectedCourse={setSelectedCourse} popup={popup} - + targetScroll={model.scrollPosition} scrollContainerRef={scrollContainerRef} persistantScrolling={persistantScrolling} diff --git a/my-app/src/views/Components/CoursePagePopup.jsx b/my-app/src/views/Components/CoursePagePopup.jsx index 25c2de2e..6bec3b24 100644 --- a/my-app/src/views/Components/CoursePagePopup.jsx +++ b/my-app/src/views/Components/CoursePagePopup.jsx @@ -1,16 +1,36 @@ import React, { useEffect, useRef, useState } from 'react'; +import RatingComponent from "./RatingComponent.jsx"; +import { model } from "../../model.js"; function CoursePagePopup({ - favouriteCourses, - handleFavouriteClick, - isOpen, - onClose, - course, - prerequisiteTree, - reviewPresenter, -}) { + favouriteCourses, + handleFavouriteClick, + isOpen, + onClose, + course, + prerequisiteTree, + reviewPresenter, + }) { + const treeRef = useRef(null); const [showOverlay, setShowOverlay] = useState(true); + const [averageRating, setAverageRating] = useState(null); + + + useEffect(() => { + const fetchAverageRating = async () => { + try { + const avg = await model.getAverageRating(course.code); + setAverageRating(avg); + } catch (error) { + setAverageRating(null); + } + }; + + if (isOpen && course) fetchAverageRating(); + + }, [isOpen, course]); + useEffect(() => { const handleKeyDown = (event) => { @@ -53,20 +73,21 @@ function CoursePagePopup({

{course.code} - {course.name} - ({course.credits} Credits) - + ({course.credits} Credits) +

-
+
+ +
+ {averageRating !== null ? ( +

+ Average Rating: {averageRating} / 5 +

+ ) : ( +

+ No Reviews Yet +

+ )} + +
+ {/* Description Section */}

Course Description

@@ -102,26 +154,25 @@ function CoursePagePopup({

Prerequisite Graph Tree

-
- {showOverlay && ( -
{ - e.stopPropagation(); - setShowOverlay(false); - }} - > -
- )} +
+ {showOverlay && (
- {prerequisiteTree} -
+ className="absolute inset-0 z-10 bg-indigo-200/10 rounded-lg cursor-pointer flex items-center justify-center z-51" + onClick={(e) => { + e.stopPropagation(); + setShowOverlay(false); + }} + >
+ )} +
+ {prerequisiteTree}
+
{/* Reviews Section (optional) */} {reviewPresenter && ( diff --git a/my-app/src/views/Components/CourseViewComponents/ModalComponent.jsx b/my-app/src/views/Components/CourseViewComponents/ModalComponent.jsx index e5f50f2b..5f52a069 100644 --- a/my-app/src/views/Components/CourseViewComponents/ModalComponent.jsx +++ b/my-app/src/views/Components/CourseViewComponents/ModalComponent.jsx @@ -1,30 +1,2 @@ -// Modal.jsx -import React from 'react'; -import ReactDOM from 'react-dom'; - -const Modal = ({ isOpen, onClose, children }) => { - if (!isOpen) return null; - - return ReactDOM.createPortal( -
-
e.stopPropagation()} - > - {children} - -
-
, - document.getElementById('modal-root') - ); -}; - -export default Modal; +import {DevSupport} from "@react-buddy/ide-toolbox"; +import {ComponentPreviews, useInitial} from "../../../dev/index.js"; \ No newline at end of file diff --git a/my-app/src/views/Components/StarComponent.jsx b/my-app/src/views/Components/StarComponent.jsx index c9af2a1f..6ee2acfa 100644 --- a/my-app/src/views/Components/StarComponent.jsx +++ b/my-app/src/views/Components/StarComponent.jsx @@ -25,11 +25,11 @@ const StarComponent = ({ index, rating, onRatingChange, onHover, readOnly = fals {!readOnly && ( <>
+