CSC/ECE 517 Spring 2024 - E2429 Reimplement student task list

From Expertiza_Wiki
Jump to navigation Jump to search

Github repository

Front end:
https://github.com/ychen-207523/reimplementation-front-end

Back end:
https://github.com/ychen-207523/reimplementation-back-end

Introduction

The Expertiza application is a collaborative platform used by students and instructors for managing assignments, peer reviews, and feedback. The current student task list interface lacks responsiveness, usability, and performance. The objective is to reimplement the frontend of the student task list using React JS and TypeScript. This reimplementation aims to enhance the user experience, improve task management, and optimize performance.

Project Requirements

The project will focus on the following features:

  1. Dynamic Task Table: Display a table listing student assignments. columns: Assignment name, course, topic, current stage, review grade, badges, stage deadline, and publishing rights.Implement sorting and filtering functionalities for efficient task navigation.
  2. Responsive Design: Prioritize accessibility and user-friendly layouts.
  3. Lazy Loading: Optimize performance by loading content only when necessary.Improve page load times for a smoother user experience.

Dummy Data

Interfaces

The interfaces were designed as a prototype for testing purposes. The future team working on the backend needs to modify it as needed.

The implementation can be found here

Duty and StudentsTeamedWith is for the side box.

Revision's purpose is undefined for now, so leave it empty, the future team should delete it if it is proved to be redundant.

IStudentTask is for the main table of student task.

export interface Duty {
  name: string;
  dueDate: string;
}

export interface Revision {

}

export interface StudentsTeamedWith {
  [semester: string]: string[];
}


// Interface for matching columns to assignment table columns
export interface IStudentTask {
  name: string;
  course_name: string;
  topic: string;
  current_stage: string;
  review_grade: {
    comment: string;
  };
  has_badge: boolean;
  stage_deadline: Date
  publishing_rights: boolean

}


JSON File

JSON File are also created for the purpose of testing, the future team should delete this file if the data can be successfully retrieved from the database.

JSON File can be found here

{
    "assignments": [
        {
            "name": "Assignment 1",
            "course_name": "CSC 517",
            "topic": "Ruby",
            "current_stage": "in progress",
            "review_grade": {
                "comment": "3/5"
            },
            "has_badge": false,
            "stage_deadline": "3/18",
            "publishing_rights": true
        },
        {
            "name": "Assignment 2",
            "course_name": "CSC 517",
            "topic": "Rails",
            "current_stage": "in progress",
            "review_grade": "N/A",
            "has_badge": true,
            "stage_deadline": "3/18",
            "publishing_rights": true
        },
        {
            "name": "Assignment 3",
            "course_name": "CSC 517",
            "topic": "Git",
            "current_stage": "in progress",
            "review_grade": "N/A",
            "has_badge": true,
            "stage_deadline": "3/18",
            "publishing_rights": false
        },
        {
            "name": "Assignment 1",
            "course_name": "CSC 522",
            "topic": "AI",
            "current_stage": "in progress",
            "review_grade": "N/A",
            "has_badge": false,
            "stage_deadline": "3/25",
            "publishing_rights": true
        },
        {
            "name": "Assignment 2",
            "course_name": "CSC 522",
            "topic": "Random Forest",
            "current_stage": "finished",
            "review_grade": "N/A",
            "has_badge": false,
            "stage_deadline": "3/28",
            "publishing_rights": true
        }
    ],
    "duties": [
        {
            "name": "CSC517-Assignment 1",
            "dueDate": "2024-03-18"
        },
        {
            "name": "CSC517-Assignment 2",
            "dueDate": "2024-03-18"
        },
        {
            "name": "CSC517-Assignment 3",
            "dueDate": "2024-03-18"
        },
        {
            "name": "CSC522-Assignment 1",
            "dueDate": "2024-03-25"
        },
        {
            "name": "CSC522-Assignment 2",
            "dueDate": "2024-03-28"
        }
    ],
    "revisions": [

    ],
    "studentsTeamedWith": {
        "CSC540, Fall 2023": ["classmate one", "classmate two", "classmate three", "classmate four"],
        "CSC515, Spring 2024": ["classmate five", "classmate six", "classmate seven"]
    }
}

Changes

Student Task Page

Dummy Data

The fetching of the data and rendering of the table slightly differs due to the fact that data is being called from a local JSON file as apposed to being fetched via an API.

The UI is contained within the file src/pages/StudentTasks/StudentTasks.tsx

Data is imported from the JSON file in the following as follows:

import testData from './assignments.json';
Therefore the table data is defined as follows:

const tableData = useMemo(() => testData.assignments, []);

However when actually importing from the backend the following import will likely be used:

// Removed useAPI import as it's currently not needed for JSON data. Uncomment when fetching data from an API.
import useAPI from "hooks/useAPI";

This should be the API call to the backend to retrieve the student tasks from the DB.

// Commented out useEffect for fetching student tasks from an API. Uncomment when needed.
  /*
  useEffect(() => {
    fetchStudentTasks({ url: '/assignments' }); // Verify this is the correct endpoint
  }, [fetchStudentTasks]);
  */

Main Table

UI changes

Previous UI:

Current UI:

Changes

  • Create new student assignments / tasks table utilizing TypeScript to implement main features and functionalities prior version.
  • Added a search box at the top for searching any input related to assignments.
  • Added a dropdown for every column to filter assignments accordingly.
  • Added a page navigation feature under the table.

Code Changes

New TypeScript Student Tasks Table

There are three main files associated with the rendering of this table and the Student Tasks UI as a whole:

1) src/pages/StudentTasks/StudentTasks.tsx

  Renders the UI and contains the logic for the table.

2) src/pages/StudentTasks/StudentTaskColumns.tsx

  Defines the table's columns.

3) src/components/Table/Table.tsx

  Defines the base logic for the table implementation.
Dropdown Feature & Developer Implications + UI Guidance

We edited the class src/components/Table/Table.tsx to have a accommodate three different options for searching the columns: a dropdown, a search box (input) or neither.

in Interface TableProps see:


// Table.tsx

interface TableProps {
  // other properties...

  // new property for search control
  columnSearchMode?: 'none' | 'input' | 'dropdown';
  dropdownOptions?: Record<string, string[]>; // if 'dropdown', provide options for each column
}

We defined the default column search property to be a search box (input) as seen below:


// Table.tsx

const Table: React.FC<TableProps> = ({
  // other properties...
  columnSearchMode = 'input' // default when creating a Table 
  
})

As a feature of our implementation and design, the developer has options for choosing which columnSearchMode to implement:

Currently, columnSearchMode is parameterized with the dropdown option as seen in UI of table above.

Therefore when rendering the table in StudentTasks.tsx, columnSearchMode can be parametrized with 3 different options based on the developer's choosing.


// StudentTasks.tsx

 <Table
            data={tableData}
            columns={tableColumns}
            columnSearchMode={'dropdown'} // Can be 'none', 'input', or 'dropdown'
            dropdownOptions={dropdownOptions}
            headerCellStyle={{background: "#f2f2f2"}}
            // ... other props
          />

Parameterizing columnSearchMode with 'none' or 'input' is as simple as entering said parameter name, nothing else is required and the UI should render.

However, for 'dropdown', the dropdownOptions prop needs to be included to pull data for the dropdown.

Style Changes

We found that the table utilized by the refactored Expertiza required a few styling changes to match that of the original one. In order to maintain the alternating style of the table, we overrode the style of the header and the bootstrap class with css:


.student-tasks .table-striped>tbody>tr:nth-of-type(odd)>td,
.student-tasks .table-striped>tbody>tr:nth-of-type(odd)>th {
  background-color: #ffffff;
  --bs-table-bg-type: none
}

.student-tasks .table-striped>tbody>tr:nth-of-type(even)>td,
.student-tasks .table-striped>tbody>tr:nth-of-type(even)>th {
  background-color: #f2f2f2;
  --bs-table-bg-type: none;
}

Where we use .student-tasks to restrict the change to the student task list, then use selectors to overwrite the color of even and odd rows of the table.

Additionally, we added a headerCellStyle prop to the Table to enable consuming code to specify the color, font color, or any other style of the header when creating a table. This was used in our code to overwrite the header background color and is demonstrated in our Table component "headerCellStyle" prop:

        <Suspense fallback={<span>Loading table...</span>}>
          <Table
            data={tableData}
            columns={tableColumns}
            columnSearchMode={'dropdown'} // Can be 'none', 'input', or 'dropdown'
            dropdownOptions={dropdownOptions}
            headerCellStyle={{background: "#f2f2f2"}}
            // ... other props
          />
        </Suspense>

Side Table

UI changes

Previous UI:

Current UI:

Changes

  • Transfer all the code from Ruby on Rails views to React and TypeScript.
  • Delete the line between Course Name and the start of team member list
  • Add the line between the end of team member list and Course Name

Code changes

This task was implemented mainly in two files: StudentTasks.css and StudentTasksBox.tsx

  • StudentTasks.css

This file contains the majority of the styles of this box.

  • StudentTasksBox.tsx

First, defined interface for the table data and pass these data as parameters

interface StudentTasksBoxParameter {
  duties: Duty[];
  revisions: Revision[];
  studentsTeamedWith: StudentsTeamedWith;
}

We also need to define a function to calculate days left with the the current time and due time

  const calculateDaysLeft = (dueDate: string) => {
    const today = new Date();
    const date = new Date(dueDate);
    const differenceInTime = date.getTime() - today.getTime();
    const differenceInDays = Math.ceil(differenceInTime / (1000 * 3600 * 24));
    return differenceInDays > 0 ? differenceInDays : 0;
  };

Next, we need to filter out those assignments that have not yet reached their due date.

const tasksNotStarted = duties.filter(duty => (calculateDaysLeft(duty.dueDate) > 0))

Finally, we can display the data as intended template

Task not yet start

<strong>
          <span className="tasknum"> {tasksNotStarted.length} </span> Tasks not yet started<br />
      </strong>
      <br />
      {tasksNotStarted.map(task => {
        const daysLeft = calculateDaysLeft(task.dueDate);
        const dayText = daysLeft > 1 ? 'days' : 'day';
        return (
          <div>
            <span>   » {task.name} ({daysLeft} {dayText} left)</span>
          </div>
        )
      })}


Student who have teamed with you

<strong>
        Students who have teamed with you<br />
      </strong>
      <br />

      {
        Object.entries(studentsTeamedWith).map(([semester, students]) => (
          <div>
            <strong>  <span className="tasknum"> 
              {students.length} </span>
                {semester}
              </strong><br />
            {students.map((student => (
              <div>
                <span className="notification">  » {student} </span>
              </div>
            )))}
            <br/>
          </div>
        ))
      }

Lazy Loading

In order to increase website responsiveness and improve overall performance, several optimizations can be made. One optimization implemented involved the use of lazy loading components, delaying sending components to the client until the client page "needs" them.

By default, React applications typically bundle all javascript files so that the user has all necessary files it needs to explore and interact with the whole site. This isn't a problem for small applications, but as the size of the application grows, the user may be sent more and more data at first load, meaning there is a longer initial load time and that the user may waste network bandwidth to get code for pages they will never visit.

Lazy loading solves this issue by allowing developers to delay the loading of components until they are first used. Any components which are loaded lazily are not sent on initial page load, but instead sent only when the browser needs them to render a view. This can result in more network transactions between the client and server, but can greatly reduce the size of these transactions, particularly the first one. Since this is a common problem, React offers a simple API for it, allowing users to mark components as lazy, surround them with a <Suspense> tag and give a fallback component to render until the data has been fetched for the component itself.

https://react.dev/reference/react/lazy

To improve the responsiveness of the site, we decided to use lazy loading for the components of the student task list. The page is primarily made up of a Table component (which renders the student task table) and a StudentTasksBox component, which renders a summary of the student's important information in a box on the left side of the screen. To make these changes, we just had to change how the components were imported and wrap the rendering of the components in the appropriate component (Suspense).

Lazy Loading of the Table and StudentTasksBox:


// Load components lazily. 
const Table = lazy(() => fakeDelay(import('../../components/Table/Table')));
const StudentTasksBox = lazy(() => fakeDelay(import('./StudentTasksBox')));

Component Wrapping for StudentTasksBox:

        <Suspense fallback={<span>Loading table...</span>}>
          <Table
            data={tableData}
            columns={tableColumns}
            columnSearchMode={'dropdown'} // Can be 'none', 'input', or 'dropdown'
            dropdownOptions={dropdownOptions}
            headerCellStyle={{background: "#f2f2f2"}}
            // ... other props
          />
        </Suspense>


Component Wrapping for of the StudentTasksBox:

        <Suspense fallback={<span>Loading Task box...</span>}>
          <StudentTasksBox
            duties={duties}
            revisions={taskRevisions}
            studentsTeamedWith={studentsTeamedWith} />
        </Suspense>

As shown above, we gave a JSX tag to the fallback prop to indicate what should be rendered until the component has finished loading.

As a result of these changes, the StudentTasksBox and Table components are not loaded by the application until the user navigates to the student_tasks page, meaning the user will experience faster initial load times, decreased browser memory usage, and may experience less network bandwidth if the entirety of the application isn't visited in a given session.

Current View

After making all of the updates and reimplementing the page, the new student task list page looks like this:


For reference, this is the old Expertiza:

As shown above, we have re-implemented all existing functionality and added some improvements, like dropdown filters and a search function.

The updated UI preserves many of the styling elements from the existing Expertiza, including color schemes and formatting, while making the page appear more compact and information-dense as well.

See the changes section for more specific one-to-one UI comparisons between the refactored version and the old Expertiza.

Future Scope

As mentioned in the CHANGES section, we used test data for our tables. The data is called from a JSON file in the same folder as our Student Task UI src/pages/StudentTasks/assignments.json.

In order to call data from the SQL server running on the backend, we need to retrieve it using API endpoints. Some of tables may need added or deleted columns to appropriately coincide with the current table column structure.

Moreover, some of the imports and API calls are already coded into src/pages/StudentTasks/StudentTasks.tsx but not complete as it was not necessary for merely changing the front end portion of the project.

useAPI import commented out:


// StudentTasks.tsx

// Removed useAPI import as it's currently not needed for JSON data. Uncomment when fetching data from an API.
// import useAPI from "hooks/useAPI"


We will likely need to create a constant that coincides with an API call to the assignments table, and make sure that table has all the necessary columns.



// StudentTasks.tsx


const StudentTasks = () => {
  // These hooks can be uncommented and used when integrating API calls
  // const { error, isLoading, data: studentTasks, sendRequest: fetchStudentTasks } = useAPI();
  // const { data: coursesResponse, sendRequest: fetchCourses } = useAPI();

  const duties = testData.duties;
  const taskRevisions = testData.revisions;
  const studentsTeamedWith = testData.studentsTeamedWith;

  // Commented out useEffect for fetching student tasks from an API. Uncomment when needed.
  /*
  useEffect(() => {
    fetchStudentTasks({ url: '/assignments' }); // Verify this is the correct endpoint
  }, [fetchStudentTasks]);
  */


Making sure the data being called is from the SQL server is likely more complicated since it is an API call vs a direct call to a local file. The constant tableData will likely need to be altered in a way that allows for proper fetching of data via the API.

// StudentTasks.tsx


// Memoize the table data to use assignments from testData
  const tableData = useMemo(() => testData.assignments, []);


Finally when calling data from the SQL server, we’ll likely need to handle errors of the API calls.


// StudentTasks.tsx

// Render the component with the Table component and necessary controls
  return (
    <div className="student-tasks">
      <Container fluid className="px-md-4">
        {/* Uncomment and adjust error handling when integrating with API calls
        {error && <div className="alert alert-danger" role="alert">{error}</div>}
        */}
        <Row className="mb-2">
          <Col>
            <h2>Assignments</h2>
          </Col>
        </Row>
// ...rest of table rendering

Team

Students:

David White (dawhite4@ncsu.edu)

Henry McKinney (hmckinn@ncsu.edu)

Yunfei Chen (ychen267@ncsu.edu)


Mentor:

Kashika Malick (kmalick@ncsu.edu)