diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 81267b247eb..9d390cbf9ad 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -1,6 +1,8 @@ name: Java CI -on: [push, pull_request] +on: + push: + pull_request: jobs: build: @@ -21,9 +23,12 @@ jobs: - name: Merge to master run: git checkout --progress --force ${{ github.sha }} + - name: Give execute permissions to Gradle wrapper + run: chmod +x ./gradlew + - name: Run repository-wide tests if: runner.os == 'Linux' - working-directory: ${{ github.workspace }}/.github + working-directory: ${{ github.workspace }}/.github run: ./run-checks.sh - name: Validate Gradle Wrapper diff --git a/.gitignore b/.gitignore index 71c9194e8bd..79af954c8c2 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,6 @@ src/test/data/sandbox/ # MacOS custom attributes files created by Finder .DS_Store docs/_site/ +*.fxml +*.class +*.css diff --git a/README.md b/README.md index 13f5c77403f..c0db07e8e3b 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,8 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) +[![Java CI](https://github.com/AY2223S2-CS2103T-W13-4/tp/actions/workflows/gradle.yml/badge.svg)](https://github.com/AY2223S2-CS2103T-W13-4/tp/actions/workflows/gradle.yml) ![Ui](docs/images/Ui.png) -* This is **a sample project for Software Engineering (SE) students**.
- Example usages: - * as a starting point of a course project (as opposed to writing everything from scratch) - * as a case study -* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details. - * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big. - * It comes with a **reasonable level of user and developer documentation**. -* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...). -* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**. +* TutorPro is an application designed specifically for private tutors to help them manage their student information effectively. The app offers a Graphical User Interface (GUI) with easy-to-use menus and buttons, as well as a Command Line Interface (CLI) for faster access. +* With TutorPro, tutors can keep track of student details, lesson schedules, homework assignments, and progress all in one central location. The app streamlines workflow, making tutoring experiences more efficient, whether for a few or many students. +* The [TutorPro product website](https://ay2223s2-cs2103t-w13-4.github.io/tp/) provides detailed documentation, and the project is part of the se-education.org initiative, based on the AddressBook-Level3 project. * This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info. diff --git a/build.gradle b/build.gradle index 108397716bd..505eab69cd9 100644 --- a/build.gradle +++ b/build.gradle @@ -40,6 +40,10 @@ task coverage(type: JacocoReport) { } } +run { + enableAssertions = true +} + dependencies { String jUnitVersion = '5.4.0' String javaFxVersion = '11' @@ -66,7 +70,7 @@ dependencies { } shadowJar { - archiveFileName = 'addressbook.jar' + archiveFileName = 'TutorPro.jar' } defaultTasks 'clean', 'test' diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index d618671b832..67ddc0ea226 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -309,7 +309,7 @@ - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[homepage](https://blog.zyf.ninja/)] +[[github](https://github.com/Yufannnn)] +[[portfolio](team/yufannnn.md)] -* Role: Project Advisor +* Role: UI Design, Task Manager +* Responsibilities: manage and distribute tasks, design UI, implementation, Developer Guide, UML Diagrams -### Jane Doe +### Niu Boqian - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/NBQian)] +[[portfolio](team/nbqian.md)] -* Role: Team Lead -* Responsibilities: UI +* Role: coder +* Responsibilities: code -### Johnny Doe +### Sze Jian Cheng - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](http://github.com/szejiancheng)] [[portfolio](team/szejiancheng.md)] -* Role: Developer -* Responsibilities: Data +* Role: coder +* Responsibilities: UML diagrams, implementation, ~~and providing snacks~~ -### Jean Doe +### Muhammad Fahim Tajwar - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/fahim-tazz)] +[[portfolio](team/fahim-tazz.md)] -* Role: Developer -* Responsibilities: Dev Ops + Threading - -### James Doe - - - -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] - -* Role: Developer -* Responsibilities: UI +* Role: Development, User and Developer Documentation, and Testing +* Responsibilities: Developer Guide, User Guide, Unit and Integration Testing, Student Info Implementation diff --git a/docs/DevOps.md b/docs/DevOps.md index d2fd91a6001..f852f8ed970 100644 --- a/docs/DevOps.md +++ b/docs/DevOps.md @@ -73,7 +73,7 @@ Any warnings or errors will be printed out to the console. Here are the steps to create a new release. -1. Update the version number in [`MainApp.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java). +1. Update the version number in [`MainApp.java`](https://github.com/AY2223S2-CS2103T-W13-4/tp/blob/master/src/main/java/seedu/address/MainApp.java). 1. Generate a fat JAR file using Gradle (i.e., `gradlew shadowJar`). -1. Tag the repo with the version number. e.g. `v0.1` +1. Tag the repo with the version number. e.g. `v1.2` 1. [Create a new release using GitHub](https://help.github.com/articles/creating-releases/). Upload the JAR file you created. diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 46eae8ee565..1a898972c7d 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -2,20 +2,75 @@ layout: page title: Developer Guide --- -* Table of Contents -{:toc} + +TutorPro is a **desktop app designed to help private tutors manage their student information effectively**. With TutorPro, tutors can easily keep track of their students' addresses, contact details, lessons, homework, and progress, all in one place. This app is optimized for use via a Graphical User Interface (GUI), allowing tutors to interact with the app using easy-to-understand buttons and menus. However, TutorPro also provides a Command Line Interface (CLI) for those who prefer a faster way of getting things done. Whether you're managing a handful of students or hundreds, TutorPro can help you streamline your workflow and make your tutoring experience more efficient. + +-------------------------------------------------------------------------------------------------------------------- + +## Table of Contents + +- [Table of Contents](#table-of-contents) +- [**Acknowledgements**](#acknowledgements) +- [**Setting up, getting started**](#setting-up-getting-started) +- [**Design**](#design) + * [Architecture](#architecture) + * [UI component](#ui-component) + * [Logic component](#logic-component) + * [Model component](#model-component) + * [Storage component](#storage-component) + * [Common classes](#common-classes) +- [**Implementation**](#implementation) + * [Quick Access Buttons on Each Student Card](#quick-access-buttons-on-each-student-card) + + [Motivation for Quick Access Buttons](#motivation-for-quick-access-buttons) + + [Implementation of Quick Access Buttons](#implementation-of-quick-access-buttons) + + [Alternatives considered for Quick Access Buttons](#alternatives-considered-for-quick-access-buttons) + * [Create Lesson feature](#create-lesson-feature) + * [School and GradeLevel Fields](#school-and-gradelevel-fields) + + [v1.2 Implementation of School and GradeLevel Fields](#v12-implementation-of-school-and-gradelevel-fields) + + [New Implementation of School and GradeLevel Fields in v1.3](#new-implementation-of-school-and-gradelevel-fields-in-v13) + * [\[Proposed\] Undo/redo feature](#proposed-undoredo-feature) + + [Proposed Implementation](#proposed-implementation) + + [Design considerations:](#design-considerations) +- [**Documentation, logging, testing, configuration, dev-ops**](#documentation-logging-testing-configuration-dev-ops) +- [**Appendix: Requirements**](#appendix-requirements) + * [Product scope](#product-scope) + * [User stories](#user-stories) +- [**Use cases**](#use-cases) + * [Add a new student](#add-a-new-student) + * [Delete a student](#delete-a-student) + * [Update a student's particulars](#update-a-students-particulars) + * [View a student's profile](#view-a-students-profile) + * [Assign homework for a student](#assign-homework-for-a-student) + * [View a Student's Homework](#view-a-students-homework) + * [Mark a Homework as DONE](#mark-a-homework-as-done) + * [Mark a Homework as NOT DONE](#mark-a-homework-as-not-done) + * [View a particular Student's Lessons](#view-a-particular-students-lessons) + * [View all Students' Lesson history](#view-all-students-lesson-history) + * [Add Lesson](#add-lesson) + * [Add Exam](#add-exam) + * [View a particular Student's Exams](#view-a-particular-students-exams) + * [View all Students' Exams](#view-all-students-exams) +- [**Non-Functional Requirements**](#non-functional-requirements) +- [**Glossary**](#glossary) +- [**Appendix: Instructions for manual testing**](#appendix-instructions-for-manual-testing) + * [Launch and shutdown](#launch-and-shutdown) + * [Deleting a student](#deleting-a-student) + * [Saving data](#saving-data) +- [**Appendix: Effort**](#appendix-effort) + +Table of contents generated with markdown-toc -------------------------------------------------------------------------------------------------------------------- ## **Acknowledgements** -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +* This project is built from the AddressBook-Level3 project created by the SE-EDU initiative. -------------------------------------------------------------------------------------------------------------------- ## **Setting up, getting started** -Refer to the guide [_Setting up and getting started_](SettingUp.md). +Refer to the guide [Setting up and getting started_](SettingUp.md). -------------------------------------------------------------------------------------------------------------------- @@ -23,7 +78,7 @@ Refer to the guide [_Setting up and getting started_](SettingUp.md).
-:bulb: **Tip:** The `.puml` files used to create diagrams in this document can be found in the [diagrams](https://github.com/se-edu/addressbook-level3/tree/master/docs/diagrams/) folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams. +:bulb: **Tip:** The `.puml` files used to create diagrams in this document can be found in the [diagrams'](https://github.com/AY2223S2-CS2103T-W13-4/tp/tree/master/docs/diagrams) folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams.
### Architecture @@ -32,11 +87,11 @@ Refer to the guide [_Setting up and getting started_](SettingUp.md). The ***Architecture Diagram*** given above explains the high-level design of the App. -Given below is a quick overview of main components and how they interact with each other. +Given below is a quick overview of the main components and how they interact with each other. **Main components of the architecture** -**`Main`** has two classes called [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java). It is responsible for, +**`Main`** has two classes called [`Main`](https://github.com/AY2223S2-CS2103T-W13-4/tp/blob/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/AY2223S2-CS2103T-W13-4/tp/blob/master/src/main/java/seedu/address/MainApp.java). It is responsible for, * At app launch: Initializes the components in the correct sequence, and connects them up with each other. * At shut down: Shuts down the components and invokes cleanup methods where necessary. @@ -69,24 +124,24 @@ The sections below give more details of each component. ### UI component -The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java) +The **API** of this component is specified in [`Ui.java`](https://github.com/AY2223S2-CS2103T-W13-4/tp/blob/master/src/main/java/seedu/address/ui/Ui.java) ![Structure of the UI Component](images/UiClassDiagram.png) -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI. +The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `studentListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI. -The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml) +The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/AY2223S2-CS2103T-W13-4/tp/blob/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2223S2-CS2103T-W13-4/tp/blob/master/src/main/resources/view/MainWindow.fxml) The `UI` component, * executes user commands using the `Logic` component. * listens for changes to `Model` data so that the UI can be updated with the modified data. * keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands. -* depends on some classes in the `Model` component, as it displays `Person` object residing in the `Model`. +* depends on some classes in the `Model` component, as it displays `student` object residing in the `Model`. ### Logic component -**API** : [`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java) +**API** : [`Logic.java`](https://github.com/AY2223S2-CS2103T-W13-4/tp/blob/master/src/main/java/seedu/address/logic/Logic.java) Here's a (partial) class diagram of the `Logic` component: @@ -94,16 +149,17 @@ Here's a (partial) class diagram of the `Logic` component: How the `Logic` component works: 1. When `Logic` is called upon to execute a command, it uses the `AddressBookParser` class to parse the user command. -1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `AddCommand`) which is executed by the `LogicManager`. -1. The command can communicate with the `Model` when it is executed (e.g. to add a person). -1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. +2. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `AddCommand`) which is executed by the `LogicManager`. +3. The command can communicate with the `Model` when it is executed (e.g. to add a student). +4. The result of the command execution is encapsulated as a `CommandResult` object which is returned from `Logic`. -The Sequence Diagram below illustrates the interactions within the `Logic` component for the `execute("delete 1")` API call. +The Sequence Diagram below illustrates the interactions within the `Logic` component for the `execute("delete +index/1")` API call. -![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png) +![Interactions Inside the Logic Component for the `delete index/1` Command](images/DeleteSequenceDiagram.png)
:information_source: **Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. -
+the Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command: @@ -114,28 +170,21 @@ How the parsing works: * All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing. ### Model component -**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java) - - +**API** : [`Model.java`](https://github.com/AY2223S2-CS2103T-W13-4/tp/blob/master/src/main/java/seedu/address/model/Model.java) + The `Model` component, -* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object). -* stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* stores the address book data i.e., all `Student` objects (which are contained in a `UniqueStudentList` object). +* stores the currently 'selected' `Student` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. * stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects. -* does not depend on any of the other three components (as the `Model` represents data entities of the domain, they should make sense on their own without depending on other components) - -
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Tag` object per unique tag, instead of each `Person` needing their own `Tag` objects.
- - - -
+* doesn't depend on any of the other three components (as the `Model` represents data entities of the domain, they should make sense on their own without depending on other components) ### Storage component -**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java) +**API** : [`Storage.java`](https://github.com/AY2223S2-CS2103T-W13-4/tp/blob/master/src/main/java/seedu/address/storage/Storage.java) @@ -154,6 +203,195 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa This section describes some noteworthy details on how certain features are implemented. +### Quick Access Buttons on Each Student Card + +#### Motivation for Quick Access Buttons +When a `tutor` wants to view a `student's` profile, homework, lessons, or exams, they need to use the commandBox. However, the current ResultDisplay is purely text-based, making it difficult and time-consuming for `tutors` to access and digest the relevant information. + +To address this issue, we propose the implementation of Quick Access Buttons on each `Student Card`. These buttons will provide tutors with easy and quick access to the relevant `student` information. By simply clicking on the appropriate button, tutors can view the `student's` profile, homework, lessons, or exams in a more informative and visually appealing way, complete with multiple diagrams or charts. + +The inclusion of these Quick Access Buttons will not only improve the user experience for `tutors` but also save them valuable time that can be better spent on other aspects of their teaching. With the information at their fingertips, `tutors` will be better equipped to make informed decisions and provide personalized guidance to each student. + +#### Implementation of Quick Access Buttons + +Given below is the partial class diagram of `Ui` component related to Quick Access Buttons on the Student Card. + + + +Each `Student Card` contains exactly four buttons `Profile`, `Homework`, `Lessons`, and `Exams`. Each of the buttons can generate a Special `DetailedContent`, which will be displayed in the `DetialedInfoSection` on the `MainWindow` + +Given below is the partial class diagram of `Ui` component related to Detailed Information Section on the Student Card. + + + +The `DetailedInfoSection` is made up of a `HeaderBar`, displaying the type of the current `DetailedInfoSection` and the name of the `Student` to which the information belongs. `DetailedContent`component has five subclasses extending it, namely `WelcomeContent`, `ProfileContent`, `GeneralHomeworkContent`, `GeneralLessonsContent`, `GeneralExamsContent`. + +Given below is the partial class diagram of `Ui` component related to Profile Content. + + + +Given below is the sequence diagram showing how the action of clicking the `ProfileButton` of a `Student` creates a new `ProfileContent` for that particular student + + + +**How the Profile Section is created when the Profile Button is Clicked** + +1. Based on the graph above, after the user clicks the view profile button, `StudentCard` calls `Student#getFullName()`, which then calls `Name#getFirstName()`. +2. `StudentCard` then calls `DetailedInfoSection#SetDeatiledHeaderBar()`, which then calls `DetailedInfoSection#SetDeatiledHeaderBar()`, with the `FirstName` returned from the previous calls. +3. `DetailedInfoSection` creates a new `HeaderBar` and sets it to be the new `DetailedHeaderBar` to be displayed. +4. `StudentCard` will create a new `ProfileContent` with the `Student`. +5. The `ProfileContent` constructor calls `Student#getName()`, `Student#getPhone()`, `Student#getEmail()`, `Student#getAddress()` which returns the name, phone, email, and address of the student. +6. `StudentCard` then calls `DetailedInfoSection#SetDeatiledContent()`, which then calls `DetailedInfoSection#SetDeatiledContent()`, with the `ProfileContent` created from the previous calls. +7. `DetailedInfoSection` sets it to be the new `DetailedContent` to be displayed. + +`GeneralHomeworkContent`, `GeneralLessonsContent`, `GeneralExamsContent` are further extended by subclasses: + + +* `EmptyHomeworkContent` and `FilledHomeworkContent` extend `GeneralHomeworkContent`, representing the situation where the `Student` has `Homework` and where the `Student` has no `Homework` respectively. + + Specifically, `FilledHomeworkContent` has `HomeworkListPanel` with `HomeworkCard` on it, showing all homework of a `Student` and a `HomeworkPieChart`, reflecting the ratio of completed and pending `Homework` of a `Student.` + + Given below is the partial class diagram of `Ui` component related to Filled Homework Content. + + + + Given below is the sequence diagram showing how the action of clicking the `HomeworkButton` of a `Student` creates a new `HomeworkContent` for that particular student + + + + **How the Filled Homework Section is created when the Homework Button is Clicked** + + 1. Based on the graph above, after the user clicks the view homework button, `StudentCard` calls `Student#getFullName()`, which then calls `Name#getFirstName()`. + 2. `StudentCard` then calls `DetailedInfoSection#SetDeatiledHeaderBar()`, which then calls `DetailedInfoSection#SetDeatiledHeaderBar()`, with the `FirstName` returned from the previous calls. + 3. `DetailedInfoSection` creates a new `HeaderBar` and sets it to be the new `DetailedHeaderBar` to be displayed. + 4. If `Student` does not have `Homework`, `StudentCard` will create a new `EmptyHomeorkContent` with the `Student`. + 5. If `Student` have `Homework`, `StudentCard` will create a new `FilledHomeorkContent` with the `Student`. + 6. The `FilledHomeworkContent` constructor calls `Student#getName()`, `Student#getHomeworkPiechartData()`, `Student#HomeworkList()`, which returns the name, Pie Chart Data, and Homework List. + 7. `StudentCard` then calls `DetailedInfoSection#SetDeatiledContent()`, which then calls `DetailedInfoSection#SetDeatiledContent()`, with the `EmptyHomeworkContent` or `FilledHomeworkContent`created from the previous calls. + 8. `DetailedInfoSection` sets it to be the new `DetailedContent` to be displayed. + +* `EmptyLessonsContent` and `FilledLessonsContent` extend `GeneralLessonsContent`, representing the situation where the `Student` has `Lessons` and where the `Student` has no `Lessons` respectively. + + Specifically, `FilledLessonsContent` has `PastLessonsListPanel` with `LessonCard` on it, showing all past `Lessons` of a `Student` and `UpcomingLessonsListPanel` with `LessonCard` on it, showing all upcoming `Lessons` of a `Student`. + + Given below is the partial class diagram of `Ui` component related to Filled Lessons Content. + + + + Given below is the sequence diagram showing how the action of clicking the `LessonsButton` of a `Student` creates a new `LessonsContent` for that particular student + + + + **How the Filled Lessons Section is created when the Lesson Button is Clicked** + + 1. Based on the graph above, after the user clicks the view lessons button, `StudentCard` calls `Student#getFullName()`, which then calls `Name#getFirstName()`. + 2. `StudentCard` then calls `DetailedInfoSection#SetDeatiledHeaderBar()`, which then calls `DetailedInfoSection#SetDeatiledHeaderBar()`, with the `FirstName` returned from the previous calls. + 3. `DetailedInfoSection` creates a new `HeaderBar` and sets it to be the new `DetailedHeaderBar` to be displayed. + 4. If `Student` does not have `Lessons`, `StudentCard` will create a new `EmptyLessonsContent` with the `Student`. + 5. If `Student` have `Lessons`, `StudentCard` will create a new `FilledLessonsContent` with the `Student`. + 6. The `FilledLessonsContent` constructor calls `Student#getName()`, `Student#getPastLessonsList()`, `Student#UpcomingLessonsList()`, which returns the name, Past Lessons List, and Upcoming Lessons List. + 7. `StudentCard` then calls `DetailedInfoSection#SetDeatiledContent()`, which then calls `DetailedInfoSection#SetDeatiledContent()`, with the `EmptyLessonsContent` or `FilledLessonsContent`created from the previous calls. + 8. `DetailedInfoSection` sets it to be the new `DetailedContent` to be displayed. + + +* `EmptyExamsContent` and `FilledExamsContent` extend `GeneralExamsContent`, representing the situation where the `Student` has `Exams` and where the `Student` has no `Exams` respectively. + + Specifically, `FilledExamsContent` has `AllExamsListPanel` with `ExamCard` on it, showing all past `Exams` of a `Student` and `UpcomingLessonsListPanel` with `ExamCard` on it, showing the most recent three upcoming `Exams` of a `Student` + + Given below is the partial class diagram of `Ui` component related to Filled Exams Content + + + + Given below is the sequence diagram showing how the action of clicking the `ExamsButton` of a `Student` creates a new `ExamsContent` for that particular student + + + + **How the Filled Exams Section is created when the Exam Button is Clicked** + 1. Based on the graph above, after the user clicks the view exams button, `StudentCard` calls `Student#getFullName()`, which then calls `Name#getFirstName()`. + 2. `StudentCard` then calls `DetailedInfoSection#SetDeatiledHeaderBar()`, which then calls `DetailedInfoSection#SetDeatiledHeaderBar()`, with the `FirstName` returned from the previous calls. + 3. `DetailedInfoSection` creates a new `HeaderBar` and sets it to be the new `DetailedHeaderBar` to be displayed. + 4. If `Student` does not have `Exams`, `StudentCard` will create a new `EmptyExamsContent` with the `Student`. + 5. If `Student` have `Exams`, `StudentCard` will create a new `FilledExamsContent` with the `Student`. + 6. The `FilledExamssContent` constructor calls `Student#getName()`, `Student#ExamsList()`, `Student#UpcomingExamsList()`, which returns the name, All Exam List, Upcoming Exams List. + 7. `StudentCard` then calls `DetailedInfoSection#SetDeatiledContent()`, which then calls `DetailedInfoSection#SetDeatiledContent()`, with the `EmptyExamsContent` or `FilledExamsContent`created from the previous calls. + 8. `DetailedInfoSection` sets it to be the new `DetailedContent` to be displayed. + +The following activity diagram summarises how the UI responds to a click button command. + + + +#### Alternatives considered for Quick Access Buttons + +While designing the Quick Access Buttons on the Student Card, several alternatives were considered to ensure an optimal user experience. + +* **Alternative 1 :** One alternative that was considered was to have a pop-up window for each button click event. However, this alternative was ultimately rejected due to its potential drawbacks. + * Pros: + * Provides a clear and separate window to display the detailed information for each button. + * It Can be designed to provide a consistent layout and structure for displaying the detailed information. + * It Can be a good choice if there is a need to provide a lot of detailed information for each button click. + * It Can be helpful in providing a larger viewing area for the detailed information. + * Cons: + * It Requires additional user interaction to close the pop-up window, which can be cumbersome and time-consuming. + * It Can be less optimized for a command-line interface (CLI), where users prefer quick and direct access to information. + * It Can lead to a cluttered user interface if multiple pop-up windows are open at the same time. + * It Can require additional resources and time to design and implement compared to other alternatives. + +* **Alternative 2 :** Another alternative that was considered was to have a separate panel for each button click event. However, this alternative was ultimately rejected due to its potential drawbacks. + * Pros: + * Provides a clear and separate panel to display the detailed information for each button. + * It Can be designed to provide a consistent layout and structure for displaying the detailed information. + * It Can be a good choice if there is a need to provide a lot of detailed information for each button click. + * It Can be helpful in providing a larger viewing area for the detailed information. + * Cons: + * It Requires additional user interaction to close the panel, which can be cumbersome and time-consuming. + * It Can be less optimized for a command-line interface (CLI), where users prefer quick and direct access to information. + * It Can lead to a cluttered user interface if multiple panels are open at the same time. + * It Can require additional resources and time to design and implement compared to other alternatives. + +### Create Lesson feature +the `new-lesson` command adds a Lesson (specified with student name, lesson name, start time, and end time) to a particular Student. Given below is an example usage scenario and how the command works: + +Step1. The user inputs a `new-lesson` command with parameters name/John, lesson/Math lesson, start/2023-05-01 1200, and end/2023-05-01 1400. When Logic is called upon to execute a command, it uses the AddressBookParser class to parse the user command. The parser recognises the command words and calls the CreateLessonCommandParser. + +Step2. The `CreateLessonCommandParser` recognises each parameter passed in. + +Step3. `CreateLessonCommandParser` creates a new `CreateLessonCommand` with appropriate parameters. + +Step4. `CreateLessonCommand` is executed by LogicManager, creating a new Lesson object and then calls the `addLesson` method in Student + +Step5. The Student calls the `addLesson` method in the UniqueLessonList, adding the Lesson to his list of Lessons. + +Step6. The result of the command execution is encapsulated as a `CommandResult` object which is returned from `Logic`. + +The Sequence Diagram below illustrates the interactions within the Logic component for to execute("new-lesson name/John lesson/Math lesson start/2023-05-01 1200 end/2023-05-01 1400") API call. + + + +The following activity diagram summarizes what happens when a user executes a CreateLessonCommand: + + + + +### School and GradeLevel Fields + +#### v1.2 Implementation of School and GradeLevel Fields + +As of v1.2, School and GradeLevel fields are parsed via the `AddCommandParser` class. `AddCommandParser#parse` method +checks whether the prefixes for school and/or gradeLevel are present, and if present, then adds it to +the new student as `Tags`. They are then treated throughout the program the same as other `Tags`. + +It has been implemented this way for its ease of implementation, and because it shows the School and GradeLevel +in the Students list view, without having to open up the student's profile. + +#### New Implementation of School and GradeLevel Fields in v1.3 + +In v1.3, we're adding School and GradeLevel as separate fields in the Student's profile, instead of `Tags`. These will +show up in the Student Profile view. + +This is to reduce the number of Tags placed under each Student's name in the Students list view, making it look less +cluttered. + + ### \[Proposed\] Undo/redo feature #### Proposed Implementation @@ -172,11 +410,11 @@ Step 1. The user launches the application for the first time. The `VersionedAddr ![UndoRedoState0](images/UndoRedoState0.png) -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. +Step 2. The user executes `delete 5` command to delete the 5th student in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. ![UndoRedoState1](images/UndoRedoState1.png) -Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. +Step 3. The user executes `add n/David …​` to add a new student. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. ![UndoRedoState2](images/UndoRedoState2.png) @@ -184,7 +422,7 @@ Step 3. The user executes `add n/David …​` to add a new person. The `add` co -Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. +Step 4. The user now decides that adding the student was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. ![UndoRedoState3](images/UndoRedoState3.png) @@ -207,7 +445,7 @@ The `redo` command does the opposite — it calls `Model#redoAddressBook()`, -Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. +Step 5. The user then decides to execute the command `list`. Commands that don't modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. ![UndoRedoState4](images/UndoRedoState4.png) @@ -229,16 +467,10 @@ The following activity diagram summarizes what happens when a user executes a ne * **Alternative 2:** Individual command knows how to undo/redo by itself. - * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). - * Cons: We must ensure that the implementation of each individual command are correct. - -_{more aspects and alternatives to be added}_ - -### \[Proposed\] Data archiving - -_{Explain here how the data archiving feature will be implemented}_ - + * Pros: Will use less memory (e.g. for `delete`, just save the student being deleted). + * Cons: We must ensure that the implementation of each command are correct. + -------------------------------------------------------------------------------------------------------------------- ## **Documentation, logging, testing, configuration, dev-ops** @@ -257,42 +489,144 @@ _{Explain here how the data archiving feature will be implemented}_ **Target user profile**: -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing to mouse interactions -* is reasonably comfortable using CLI apps +Our users: +* have a need to manage any number of students +* prefer offline desktop apps over other types +* can type commands fast +* prefer typing, with some mouse control +* prefer a more robust, aesthetic GUI, as opposed to a verbose list of students on a command line +* are reasonably comfortable using CLI -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app +**Value proposition**: Manage students' academic progress faster and more conveniently than a typical mouse/GUI driven app +-------------------------------------------------------------------------------------------------------------------- ### User stories -Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` +Priorities: +- High (must have) - `* * *` +- Medium (nice to have) - `* *` +- Low (unlikely to have) - `*` + +| Category | Priority | As a …​ | I want to …​ | So that I can…​ | +|:------------------------------------------------------------------|----------|---------|------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Track Student Homework | `* * *` | tutor | Create homework logs | I can have a list of homeworks to refer to, for each student. | +| | `* *` | tutor | Edit/Delete any past/future homework logs | I can track any changes in my students' homeworks. | +| | `* * *` | tutor | Mark homework as completed/uncompleted | I can track what needs to be done in order to give reminders to student. | +| | `* * *` | tutor | Assign homework deadlines | I can track when each homework is due, to help students finish them early. | +| | `* * *` | tutor | View completed and uncompleted homework | I can separately track pending homework. | +| | `* *` | tutor | View what proportion of my student's upcoming homework is still pending | I can urge my students to manage their homework workload. | +| Store and access students’ info (addresses, personal particulars) | `* *` | tutor | Click open a `view profile` button beside a Student to look at his/her home address and phone number | I can have easy access to the students’ information whenever needed. | +| | `* *` | tutor | Update/Delete my students' personal information as needed | I can keep my records accurate and up-to-date | +| | `* * *` | tutor | Click a button with a student’s name to view his/her upcoming school exams/assignments | Change my lesson plans accordingly to prepare the student for such tasks. For example, change all sessions of a week to Chemistry lessons to prepare the student for an upcoming Chemistry exam | +| | `*` | tutor | I can track which students are at which school and grade level | I can create materials for multiple students of the same grade level. | +| | `* *` | tutor | Click a button with a student’s name to view his/her upcoming homework, exams and assignments | I have easy access to all relevant information in one click. | +| Lesson Plan Management | `* * *` | tutor | Create lesson plans for future classes | I can always plan ahead of my lessons. | +| | `* *` | tutor | View my students’ lesson history | I can recap past lessons with my student. | +| | | tutor | View lesson history filtered by subjects | I can recap past lessons based on different subjects. | +| | `* *` | tutor | View the date and time of a past lesson | I can see how many lessons I’ve had over a period of time. | +| | | tutor | Edit/Delete lessons | My lesson plan is always up to date. | +| Track Student Exams | `* *` | tutor | Assign and view school exam dates to my students | I can keep track of how long my student has to prepare for an exam. | +| | `* *` | tutor | View all my students' exams at a glance | I can provide extra instruction to students that have exams coming up. | + + +-------------------------------------------------------------------------------------------------------------------- + +## Use cases + + +### Add a new student + +**MSS** + +1. User enters new-student command with NAME, ADDRESS, PHONE, EMAIL and (optionally) School, Grade Level. +2. TutorPro adds the new student to the student list. + + Use case ends. + +**Extensions** + +* 2a. A student with the same name already exists. + * 2a1. TutorPro notifies the user of a duplicate. + * 2a2. TutorPro continues to take input. + + + Use case continues at step 1. + +* 2b. The wrong formatting was used. + * 2b1. TutorPro notifies the user of wrong formatting. + * 2b2. TutorPro continues to take input. + + + Use case continues at step 1. + + +### Delete a student + +**MSS** + +1. User requests to list students +2. TutorPro shows a list of students +3. User requests to delete a specific student in the list +4. TutorPro deletes the student + + Use case ends. + +**Extensions** + +* 2a. The list is empty. + + Use case ends. + +* 3a. The given index is invalid. + + * 3a1. TutorPro shows an error message. + + + Use case resumes at step 2. + + +### Update a student's particulars + +**MSS** + +1. User enters update-info command with any of the optional fields: NAME, ADDRESS, PHONE, EMAIL, SCHOOL, GRADE LEVEL. +2. TutorPro updates the student's particulars. + Use case ends. + +**Extensions** + +* 2a. The wrong formatting was used. + * 2b1. TutorPro notifies the user of wrong formatting. + * 2b2. TutorPro continues to take input. + Use case continues at step 1. + + +### View a student's profile + +**MSS** + +1. User requests to list students +2. TutorPro shows a list of students +3. User clicks on **Profile** button of a particular student +4. TutorPro displays the profile of that student. -| Priority | As a …​ | I want to …​ | So that I can…​ | -| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- | -| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App | -| `* * *` | user | add a new person | | -| `* * *` | user | delete a person | remove entries that I no longer need | -| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list | -| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident | -| `*` | user with many persons in the address book | sort persons by name | locate a person easily | + Use case ends. -*{More to be added}* +**Extensions** -### Use cases +* 1a. The list is empty. -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) + Use case ends. -**Use case: Delete a person** +### Assign homework for a student **MSS** -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +1. User requests to list students +2. TutorPro shows a list of students +3. User enters new-homework command with student's name, homework title, and deadline. +4. TutorPro assigns the homework to that particular student. Use case ends. @@ -302,26 +636,276 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli Use case ends. +* 3a. The given name doesn't exist in the list. +* 3b. The user used invalid command format. +* 3c. The user provided a date that is earlier than the current date. + + * 3*1. TutorPro shows an error message. + + Use case resumes at step 3. + + +### View a Student's Homework + +**MSS** + +1. User enters view-homework command with a student's name. +2. TutorPro shows all the completed/pending homework of that student. + + Use case ends. + +**Extensions** + +* 1a. Alternatively, the user clicks on the **Homework** button next to the student name. + * 1a1. TutorPro displays homework information on the Detailed Information section. + +### Mark a Homework as DONE + +**MSS** + +1. User clicks on **Homeworks** button of a student. +2. TutorPro shows that student's homeworks. +3. User enters mark-homework with student name, and homework index. +4. TutorPro updates the status of that homework as DONE. + + Use case ends. + +**Extensions** + +* 2a. The student's homework list is empty. + + Use case ends. + +* 3a. The given index is invalid. + + * 3a1. TutorPro shows an error message. + + Use case resumes at step 3. + + +### Mark a Homework as NOT DONE + +**MSS** + +1. User clicks on **Homeworks** button of a student. +2. TutorPro shows that student's homeworks. +3. User enters unmark-homework command with student name, and homework index. +4. TutorPro updates the status of that homework as NOT DONE. + + Use case ends. + +**Extensions** + +* 2a. The student's homework list is empty. + + Use case ends. + * 3a. The given index is invalid. - * 3a1. AddressBook shows an error message. + * 3a1. TutorPro shows an error message. + + Use case resumes at step 3. + +### View a particular Student's Lessons + +**MSS** + +1. User clicks on **Lessons** button of a student. +2. TutorPro shows that student's past and upcoming Lessons. + + Use case ends. + +**Extensions** + +* 2a. The student's lesson list is empty. + + Use case ends. + +### View all Students' Lesson history + +**MSS** + +1. User enters view-lesson command without any arguments. +2. TutorPro shows all students' past and upcoming Lessons. + + Use case ends. + +**Extensions** + +* 2a. None of the students have any lessons. + * 2a1. TutorPro shows an empty Lesson list. - Use case resumes at step 2. + Use case ends. + +### Add Lesson + +**MSS** + +1. User enters new-lesson command with student name, lesson title and start and end times. +2. TutorPro adds the lesson to that student's Lesson list. + + Use case ends. + +**Extensions** + +* 1a. The named student doesn't exist + * 1a1. TutorPro returns an error message. + + Use case resumes at 1. + +* 1b. The start/end time is in the past. + * 1b1. TutorPro returns an error message. + + Use case resumes at 1. + +* 1c. The start/end time format is invalid. + * 1c1. TutorPro returns an error message showing the accepted time formats. + + Use case resumes at 1. + +### Add Exam + +**MSS** + +1. User enters new-exam command with student name, exam title and start and end times. +2. TutorPro adds the exam to that student's exam list. + + Use case ends. + +**Extensions** + +* 1a. The named student doesn't exist + * 1a1. TutorPro returns an error message. + + Use case resumes at 1. + +* 1b. The start/end time is in the past. + * 1b1. TutorPro returns an error message. + + Use case resumes at 1. + +* 1c. The start/end time format is invalid. + * 1c1. TutorPro returns an error message showing the accepted time formats. + + Use case resumes at 1. + +### View a particular Student's Exams + +**MSS** + +1. User clicks on **Exams** button of a student. +2. TutorPro shows that student's past and upcoming Exams. + + Use case ends. + +**Extensions** + +* 2a. The student's exam list is empty. + + Use case ends. + +### View all Students' Exams + +**MSS** + +1. User enters view-exam command without any arguments. +2. TutorPro shows all students' past and upcoming exams. -*{More to be added}* + Use case ends. -### Non-Functional Requirements +**Extensions** + +* 2a. None of the students have any lessons. + * 2a1. TutorPro shows an empty Lesson list. + + Use case ends. + +-------------------------------------------------------------------------------------------------------------------- + +## Non-Functional Requirements 1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed. -2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. +2. Should be able to hold up to 1000 students without a noticeable sluggishness in performance for typical usage. 3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. +4. Commands should be easy to remember(close to English words) and not cryptic for non-tech users to remember. +5. Commands shouldn't be verbose. +6. Results should be easy to read for non-tech users (e.g. shouldn't look like long, cluttered log files). -*{More to be added}* - -### Glossary +-------------------------------------------------------------------------------------------------------------------- +## Glossary +* **Student**: A student who is taking lessons from the Tutor. +* **Tutor**: A student who is teaching the Student. Here, the Tutor is also the user of the application. +* **Lesson**: A meeting between the Tutor and the Student. +* **Exam**: A test taken by the Student. +* **Homework**: A task given to the Student by the Tutor. +* **TutorPro**: The name of the application. * **Mainstream OS**: Windows, Linux, Unix, OS-X -* **Private contact detail**: A contact detail that is not meant to be shared with others + +-------------------------------------------------------------------------------------------------------------------- + +## Planned Enhancements + +### Properly Handle Long Names +* Issue: Currently, if the student's name are too long, on the student card, the name will be truncated. The full name can only be seen by view-profile command, and via the Clicking the profile button on the student card. +* Possible Solution to Implement in the Future: + * Truncate the name to a certain length, and add an ellipsis at the end. + * Add a tooltip to the name label, so that the full name can be seen when the user hovers over the name label. + +### Properly Handle Long Tags +* Issue: Currently, TutorPro disallows the user to add tags that exceeds 30 characters in length. +* Possible Solution to Implement in the Future: + * Truncate the tag to a certain length, and add an ellipsis at the end. + * Add a tooltip to the tag label, so that the full tag can be seen when the user hovers over the tag label. + +### Resizable Main Window +* Issue: Currently, the main window is not resizable. +* Possible Solution to Implement in the Future: + * Make the main window resizable. + * Make the main window resizable, but with a minimum size. + * Properly handle the proportion of the different components in the main window when the main window is resized. + +### Storage Encryption +* Issue: Currently, the data is stored in plain text and can be easily modified by the users. If the user make a change that can be parsed by the parser to the data, the application will not detect it but can cause unexpected behaviour. +* Possible Solution to Implement in the Future: + * Encrypt the data before storing it. + * Encrypt the data before storing it, and allow the user to set a password to decrypt the data. + * Make the data unmodifiable by the user. + +### Synchronized Commands and Detailed Information Section +* Issue: Currently, the commands are not synchronized with the detailed information section. For example, if the user updates the name of a student, the Detailed Information Section will refresh to the Welcome Page, and the user will have to click on the student card again to see the updated name. +* Possible Solution to Implement in the Future: + * Make the Detailed Information Section refresh to the corresponding profile, homework, lesson, or exam page that the user is operating on. + +### Better Error Message +* Issue: Currently, the error message is not very descriptive. For example, if the user enters an invalid command, the error message will be "Invalid command format! + [the correct format of the command]". +* Possible Solution to Implement in the Future: + * Make the error message more descriptive. For example, if the user enters an invalid command, the error message will be "Invalid command format! + [the correct format of the command] + [the reason why the command is invalid]". + * Use color to highlight the invalid part of the command. + +### Customizable Color Theme +* Issue: Currently, the color theme of the application is fixed. +* Possible Solution to Implement in the Future: + * Allow the user to customize the color theme of the application. + * Allow the user to choose from a few pre-defined color themes. + +### Customizable Font +* Issue: Currently, the font of the application is fixed. +* Possible Solution to Implement in the Future: + * Allow the user to customize the font of the application. + * Allow the user to choose from a few pre-defined fonts. + +### Empty Data File Handling +* Issue: Currently, if the user opens the application for the first time, TutorPro will show a set of sample students. However, if the user's data file is somehow corrupted, TutorPro will show an empty student list. This can be confusing for the user. +* Possible Solution to Implement in the Future: + * Show a set of sample students if the data file is corrupted, and allow the user to import the sample students into the data file. + * Show where the data file is located and how is the data file corrupted, and allow the user to fix the data file. + +### Memo for Lessons +* Issue: Currently, the user cannot add any remarks to a lesson. +* Possible Solution to Implement in the Future: + * Allow the user to add a memo to a lesson. + * Allow the user to add a memo to a lesson, and allow the user to view the memo in the Detailed Information Section. -------------------------------------------------------------------------------------------------------------------- @@ -340,38 +924,66 @@ testers are expected to do more *exploratory* testing. 1. Download the jar file and copy into an empty folder - 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. - -1. Saving window preferences + 2. use the command `java -jar TutorPro` jar file Expected: Shows the GUI with a set of sample contacts. The window + size may + not be + optimum. - 1. Resize the window to an optimum size. Move the window to a different location. Close the window. - 1. Re-launch the app by double-clicking the jar file.
- Expected: The most recent window size and location is retained. -1. _{ more test cases …​ }_ +### Deleting a student -### Deleting a person +1. Deleting a student while all students are being shown -1. Deleting a person while all persons are being shown + 1. Prerequisites: List all students using the `list` command. Multiple students in the list. - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + 2. Test case: `delete index/1`
+ Expected: First student is deleted from the list. Details of the deleted student shown in the status message. - 1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. + 3. Test case: `delete index/0`
+ Expected: No student is deleted. Error details shown in the status message. - 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. - - 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
+ 4. Other incorrect delete commands to try: `delete`, `delete index/x`, `...` (where x is larger than the list size) +
Expected: Similar to previous. -1. _{ more test cases …​ }_ ### Saving data 1. Dealing with missing/corrupted data files - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ + 1. Exit TutorPro. + 2. Open the data/addressbook.json file in a suitable text editor. + 2. Delete some lines randomly. + 3. Reopen TutorPro. + Expected: Should start with a new TutorPro with sample data. + + +-------------------------------------------------------------------------------------------------------------------- + +## **Appendix: Effort** + +Our team feels that we've put in more effort than that was required for this project, as we implemented many new features and enhancements that were not required for the project. We've also spent a lot of time on the UI, as we wanted to make the application as user-friendly as possible. We've also spent a lot of time on the documentation, as we wanted to make the documentation as detailed as possible, so that the user can easily understand how to use the application. + +We faced several difficulties in the project. + +* Difficulty managing the schedule clash of Student's lessons and exams. + * Since the Student's lessons and exams are stored in separate lists, we have to carefully handle the interactions between the two lists. We also have to make sure that our assumptions about the clashes are as realistic as possible. + * For example, we assume those no two students can have lessons at the same time but two students can sit for the same exam at the same time. + * It takes us a lot of time to come up with a design that can handle all the possible clashes. + +* Difficulty in implementing new UI + * We have to carefully handle the interactions between the different components in the UI. + * For example, we have to make sure that the Detailed Information Section is updated when the user clicks on a different student card. + * It takes us a lot of time to come up with a design that can handle all the possible interactions. + +* Difficulty in UG and DG + * The User Guide and Developer Guide underwent extensive revisions and proofreading to accommodate the numerous new entities and commands that were added to the product. These changes were necessary to ensure that users and future developers could fully comprehend how the product operates. Having comprehensive documentation is crucial in helping users and developers utilize the product effectively and efficiently, which ultimately leads to an enhanced user experience and increased adoption. It's commendable that you invested time and effort into updating and refining your guides. + +We also saved many efforts and time by reuse +* reuse date and time parser from RussellDash332's ip code + * RussellDash332's code provides a nice and need way to parse multiple formats of date and time, which saves us a lot of time. + * This reuse allows us to focus on other parts of the project. -1. _{ more test cases …​ }_ +Effort Required and Achievement of our Team +* Our team has invested significant time and effort in this project with the goal of meeting the needs of our users and enhancing their experience when using our product. By expanding on the original AB3 code base, we have made substantial progress in the development of our product, as evidenced by the lines of code we have written and number of new commands we have created. Our ultimate desire is for our users to find our product effective in addressing their needs and for future developers to express interest in contributing to its further growth and development. diff --git a/docs/UserGuide.md b/docs/UserGuide.md index e7df68b01ea..6ded0f0a74f 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -3,191 +3,982 @@ layout: page title: User Guide --- -AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps. +TutorPro is a **desktop app designed to help private tutors manage their student information effectively**. With TutorPro, tutors can easily keep track of their students' addresses, contact details, lessons, homework, and progress, all in one place. This app is optimized for use via a Graphical User Interface (GUI), allowing tutors to interact with the app using easy-to-understand buttons and menus. However, TutorPro also provides a Command Line Interface (CLI) for those who prefer a faster way of getting things done. Whether you're managing a handful of students or hundreds, TutorPro can help you streamline your workflow and make your tutoring experience more efficient. -* Table of Contents -{:toc} +-------------------------------------------------------------------------------------------------------------------- +## Table of Contents +- [Table of Contents](#table-of-contents) +- [Quick start](#quick-start) + * [Glossary](#glossary) +- [GUI](#gui) + * [Quick Access Buttons and Detailed Information Section](#quick-access-buttons-and-detailed-information-section) + + [Profile Page](#profile-page) + + [Homework Page](#homework-page) + + [Lessons Page](#lessons-page) + + [Exams Page](#exams-page) +- [Main Entities](#main-entities) + * [Homework](#homework) + * [Lesson](#lesson) + * [Exam](#exam) +- [Commands](#commands) + * [How to interpret the Command format](#how-to-interpret-the-command-format) + * [Profile Commands](#profile-commands) + + [List all Students](#list-all-students) + + [Create a new Student Profile](#create-a-new-student-profile) + + [View a Student Profile](#view-a-student-profile) + + [Update Student Information](#update-student-information) + + [Delete a Student Profile](#delete-a-student-profile) + * [Homework Commands](#homework-commands) + + [Assign Homework to a Student](#assign-homework-to-a-student) + + [View Student Homework](#view-student-homework) + + [Delete Homework from a Student](#delete-homework-from-a-student) + + [Mark the Homework of a Student as Done](#mark-the-homework-of-a-student-as-done) + + [Unmark Homework of a Student as Undone](#unmark-homework-of-a-student-as-undone) + + [Update Homework of a Student](#update-homework-of-a-student) + * [Lessons Commands](#lessons-commands) + + [Create a New Lesson Plan for the Upcoming Lesson](#create-a-new-lesson-plan-for-an-upcoming-lesson) + + [View Lessons](#view-lessons) + + [Delete a Lesson from a student](#delete-a-lesson-from-a-student) + + [Update a Lesson](#update-a-lesson) + * [Exam Commands](#exam-commands) + + [Add an Exam to be tracked](#add-an-exam-to-be-tracked) + + [Remove an exam](#remove-an-exam) + + [View exams tracked by TutorPro](#view-exams-tracked-by-tutorpro) + + [Edit exam details](#edit-exam-details) + * [Global Commands](#global-commands) + + [Get Help for TutorPro](#get-help-for-tutorpro) + + [Clear all TutorPro Data](#clear-all-tutorpro-data) + + [Exit TutorPro](#exit-tutorpro) +- [Unique Mechanisms](#unique-mechanisms) + * [Search by Name Mechanism](#search-by-name-mechanism) + * [Schedule Clash Detection Mechanism](#schedule-clash-detection-mechanism) + + [Schedule Clash Detection Mechanism when Adding a New Lesson](#schedule-clash-detection-mechanism-when-adding-a-new-lesson) + - [With respect to existing lessons](#with-respect-to-existing-lessons) + - [With respect to existing exams](#with-respect-to-existing-exams) + + [Schedule Clash Detection Mechanism when Adding a New Exam](#schedule-clash-detection-mechanism-when-adding-a-new-exam) + - [With respect to existing lessons](#with-respect-to-existing-lessons-1) + - [With respect to existing exams](#with-respect-to-existing-exams-1) + * [Duplicate Detection Mechanism](#duplicate-detection-mechanism) + + [Duplicate Homework Detection](#duplicate-homework-detection) + + [Duplicate Lesson Detection](#duplicate-lesson-detection) + + [Duplicate Exam Detection](#duplicate-exam-detection) + * [Storage Mechanism](#storage-mechanism) +- [FAQ](#faq) +- [Summary](#summary) + * [List of Commands](#list-of-commands) + * [List of Prefixes](#list-of-prefixes) + * [Supported date-time formats](#supported-date-time-formats) + * [Supported date formats](#supported-date-formats) + +Table of contents generated with markdown-toc -------------------------------------------------------------------------------------------------------------------- +
## Quick start -1. Ensure you have Java `11` or above installed in your Computer. +1. Ensure you have Java `11` or above installed on your Computer. -1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases). +2. Download the latest `TutorPro.jar` from [here](https://github.com/AY2223S2-CS2103T-W13-4/tp/releases). -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +3. Copy the file to the folder you want to use as the _home folder_ for TutorPro. -1. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar addressbook.jar` command to run the application.
- A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
+4. Open a command terminal, `cd` into the folder in which you put the jar file, and use the `java -jar TutorPro.jar` command to run the application.
+ A GUI similar to the one below should appear in a few seconds. Note how the app contains some sample data.
![Ui](images/Ui.png) -1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
- Some example commands you can try: +5. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
- * `list` : Lists all contacts. +6. Refer to the [Commands](#commands) below for details of each command. - * `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book. +-------------------------------------------------------------------------------------------------------------------- +
+ +### Glossary + +You may come across some terms you don't understand in the user guide. +The following table provides clarification of the terms commonly used in TutorPro. + +| Term | Description | +|:----------------:|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Alphanumeric** | Digits and letters only. For example, `AB3`, `TutorPro`, `coco123`, and `2103` are alphanumeric. `#01-04`, `email@domain.com`, and `white spaces` are not. | +| **Attribute** | Words that follow prefixes to describe properties, states, characteristics, and traits. Examples are price, weight, name, and order status. | +| **Command** | A command is a specific instruction you can give to TutorPro to perform an action. You can view the list of commands available [here](#list-of-commands). | +| **CLI** | Command-Line Interface (CLI) receives commands from a user in the form of lines of text. It refers to the input text box in this context. | +| **GUI** | GUI stands for Graphical User Interface. It refers to the display window of the TutorPro application. | +| **Index** | The index of the contact or item in the display list for contacts/items. | +| **Integer** | Whole number | +| **Parameter** | A parameter refers to the information you need to give to your command such that it can execute an action based on that information. | +| **Prefix** | A prefix indicates the kind of information you are keying in. You can view the list of prefixes available [here](#list-of-prefixes). | +| **Whitespace** | An empty character, or a placeholder character. | +| **Student** | A `Student` whom the user (who is a tutor) teaches | +| **Homework** | A `Homework` assignment whom the user (who is a tutor) has assigned to a `Student` | +| **Lesson** | A `Lesson` that the user has scheduled with a `Student` | +| **Exam** | An `Exam` that a Student is scheduled to sit for | - * `delete 3` : Deletes the 3rd contact shown in the current list. +-------------------------------------------------------------------------------------------------------------------- - * `clear` : Deletes all contacts. +## GUI +When you first run the app, you may see a display window pop up similar to the one below. We call this window the **Main Window**. - * `exit` : Exits the app. +
:information_source: **Note:** The current version of the window is not resizable. Resizability will be added in future versions! +
-1. Refer to the [Features](#features) below for details of each command. +![Starting Display Window](images/GUI.jpg) --------------------------------------------------------------------------------------------------------------------- +* The **Command Box** on the bottom left refers to the text field where you can type in all your commands. +* The **Display List for Students** on the top left refers to the section where all the students are displayed. +* The **Detailed Information Section** on the right refers to the section where more specific information of the profile, homework, lessons, or exams of a particular student is displayed. + +You may enter the following commands in the Command Box to see how the Display List changes: +* `list` lists all students. +* `delete index/1` deletes the student with index 1. + +You may also click the quick access buttons one each student card to see how the Detailed Information Section changes. + +
+ +### Quick Access Buttons and Detailed Information Section +Upon launching the application or executing a command, a welcome page will be displayed in the Detailed Information Section. +Each Student card has four buttons, which are profile, homework, lessons, and exams. When a user clicks on any of the buttons, the corresponding information will be presented in the Detailed Information Section. + +
:information_source: **Why is the date and time displayed in two different formats?**
+The date and time format in the Detailed Information Section is different from the one used in the `view` commands. +This is purposely done to make the information more distinct and make sure you never get confused. +
+ +#### Profile Page +Upon clicking the Profile Button, the Detailed Information section will be refreshed and show the detailed particulars of the student, including the student's full name, phone number, address, and email. + +![Starting Display Window](images/Profile.jpg) + +#### Homework Page -## Features +Upon clicking the Profile Button, the Detailed Information section will be refreshed and show the detailed homework information of the student. -
+It includes a homework list, showing all the homework information. +The icon on the left of each homework represents its status, +with the tick icon being `completed` and the cross-icon being `pending`. +It also has a pie chart which represents a ratio between completed and pending homework. -**:information_source: Notes about the command format:**
+![Starting Display Window](images/Homework.png) -* Words in `UPPER_CASE` are the parameters to be supplied by the user.
- e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. +#### Lessons Page -* Items in square brackets are optional.
- e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. +Upon clicking the Lesson Button, +the Detailed Information section will be refreshed and show the detailed Lessons Information for the student. -* Items with `…`​ after them can be used multiple times including zero times.
- e.g. `[t/TAG]…​` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. +It includes a past lessons list, showing all the past lesson information and an upcoming lessons list, +showing all the upcoming lessons. -* Parameters can be in any order.
- e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. +![Starting Display Window](images/Lessons.jpg) -* If a parameter is expected only once in the command but you specified it multiple times, only the last occurrence of the parameter will be taken.
- e.g. if you specify `p/12341234 p/56785678`, only `p/56785678` will be taken. +:bulb: **Tip:** Lesson Lists do not have indexes to avoid confusion with the indexes of the lessons in the original list. +If you want to see the index of the lessons in the list, +you can use the `view-lessons` command to view the list of lessons. -* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
+:bulb: **Tip:** This page only shows the most basic information of the lessons. If you want to see more details of the lessons, you can use the `view-lessons` command to view the list of lessons. + +#### Exams Page + +Upon clicking the Exam Button, +the Detailed Information section will be refreshed and show the detailed Exams Information for the student. + +It includes a past exams list, showing all the past exams information and all the upcoming exams list, +showing all the upcoming exams. + +![Starting Display Window](images/Exams.jpg) + +:bulb: **Tip:** Exam Lists do not have indexes to avoid confusion with the indexes of the exams in the original list. +If you want to see the index of the exams in the list, +you can use the `view-exams` command to view the list of exams. + +:bulb: **Tip:** This page only shows the most basic information of the exams. If you want to see more details of the exams, you can use the `view-exams` command to view the list of exams. + +
+ +## Main Entities +TutorPro allows you to easily handle three main entities of your students: `Homework`, `Lesson`, and `Exam`. +TutorPro also has unique mechanisms to handle duplicate entities and potential clashes between entities. +Please refer to the [Duplicate Detection Mechanism](#duplicate-detection-policy) and [Schedule Clash Detection Mechanism](#schedule-clash-detection-mechanism) sections for more details. + +### Homework +A `Homework` is an assignment that you've assigned to a `Student`. +It has a description, a deadline, and a status. +The status can be either `pending` or `completed`. + +### Lesson +A `Lesson` is a scheduled meeting between you and a `Student`. +It has a name, a start time, an end time, and a status. +The status can be either `done` or `not done`. + +### Exam +An `Exam` is a scheduled exam that a `Student` is going to sit for. +We assume that the exams are conducted by the school, instead of you. +It has a name, a start time, an end time, and a status, an optional weightage, and an optional grade. +The status can be either `done` or `not done`. + +
+:information_source: **Note:**
The status used for `Lesson` and `Exam` is different from the status used for `Homework`.
+This is because the status of `Homework` is determined by the user, (using mark as done or unmark commands) while the status of `Lesson` and `Exam` is determined by the time. +Therefore, we use different terms to avoid confusion. +
+ + +
+ +## Commands + +### How to interpret the Command format + +* The terms in `UPPER_CASE` are placeholders need to be replaced with your values. For example, in the command `new-student name/NAME`, `NAME` is a placeholder that can be substituted with a specific name, such as `new-student name/John Doe`. +* Items with `…`​ after them can be used zero or more times.
+ e.g. `[name/STUDENT_NAME]…​` can be used as ` ` (i.e. 0 times), `name/John Doe`, `name/John Doe name/Jane Doe` (2 times) etc. +* Extra parameters for commands that don't take in parameters (such as `help`, `exit,` and `clear`) will be ignored.
e.g. if the command specifies `help 123`, it will be interpreted as `help`. +* Empty parameters will cause an error. For example, `new-student name/ address/Block 414`. +* All `start/` and `end/` parameters must follow the [Supported Date Time Formats](#supported-date-time-formats). +* Unless otherwise specified, the order of prefixes doesn't matter.
+ e.g. if the command specifies `name/NAME phone/PHONE_NUMBER`, `phone/PHONE_NUMBER name/NAME` is also acceptable unless stated otherwise in a particular command. +* TutorPro allows you to execute commands on students in the entire student list, instead of just the displayed list. + For example: + - currently, the displaying list only shows one student, `Bernice Yu`. + + ![Entire List](images/entireList1.jpg) + - However, you can still execute commands to `John Doe` even though he is not displayed in the list. + + ![Entire List](images/entireList2.jpg) + - This is because `John Doe` is in the original student list, and the command will be executed on the original student list. + - ![Entire List](images/entireList3.jpg) + +
+ +### Profile Commands + +
+:information_source: **Why TutorPro uses search by index for all the Profile Commands instead of the unique search by name mechanism?**
+For profile commands, especially `delete` and `update-info` command, that may potentially change the name of the student, TutorPro uses search by index instead of search by name to avoid any confusion that may be caused by the name change.
-### Viewing help : `help` +#### List all Students -Shows a message explaning how to access the help page. +Lists all students in TutorPro. -![help message](images/helpMessage.png) +Format: `list` -Format: `help` +* This list shows the indexes of all Students in TutorPro. Use it to find the correct index to delete/edit a student. + +Example: +* `list` Lists down all students currently stored in TutorPro. + + +#### Create a new Student Profile + +Creates a new profile for a student, given the student’s name. + +Format: `new-student [name/STUDENT_NAME] [address/STUDENT_ADDRESS] [phone/PHONE] [email/EMAIL] [school/SCHOOL] [level/GRADE_LEVEL]` + +* SCHOOL and GRADE_LEVEL are optional. +* SCHOOL and GRADE_LEVEL consist of numbers and letters only (no symbols or spaces). + +Example: +* `new-student name/John Doe address/21 Prince George’s Park email/jdoe@gmail.com phone/12345678 school/ACJC level/sec8` Adds a new student named `John Doe` to TutorPro. + +:exclamation: **Caution:** `STUDENT_NAME`, `ADDRESS`, `PHONE` and `EMAIL` should appear exactly once. + + + +#### View a Student Profile + +Displays one or many students' profiles, given parameters. + +Format `view-profile [name/STUDENTS_NAME] ...` +* Shows a list of students that match the given `STUDENT_NAME` +* If no `STUDENT_NAME` is given, then shows all students. + +Example: +* `view-profile` Displays all students in the command line. +* `view-profile name/John` Displays all students that match the name `John`. +![view-profile.png](images/view-profile.png) -### Adding a person: `add` +:exclamation: **Caution:** STUDENT_NAME is case-insensitive and supports partial matching. +For example, `john` will match `John Doe` and `john doe`. You can refer to the [search by name mechanism](#search-by-name-mechanism) for more details. -Adds a person to the address book. -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +#### Update Student Information -
:bulb: **Tip:** -A person can have any number of tags (including 0) +Updates the student's information, given the student's label, fields to change, and updated field value + +Format: `update-info [index/STUDENT INDEX] [field/NEW_INFO] ...` + +Examples: +* `update-info index/1 phone/12345678` Changes the phone number of the student at index 1 to `12345678`. +* `update-info index/2 address/Block 414` Changes the address of the student at index 2 to `Block 414`. +* `update-info index/3 name/John` Changes the name of the student at index 3 to `John`. + +* `field/NEW_INFO` is to be replaced by one of + * `name/` + * `phone/` + * `address/`
+ And their respective new values. +* At least one updated parameter has to be present.
+ +
:information_source: **Why can't I update the School and Grade Level of a Student?**
+When your student changes schools or goes up a grade level, their curriculum will likely change. In that case, you should +create a fresh new profile for your student, so that all the lessons, exams and homeworks from the previous grade level/school do not +get mixed up with the new one. You may then delete the old profile. +This is purposely done to make the information less cluttered and reduce confusion.
+ + +:bulb: **Tip:** You can edit multiple fields in a student's profile at once by using several of the above prefixes. For Example: +* `update-info index/1 name/John` Changes the name of the student at index 1 to `John`. +* `update-info index/2 name/John address/Block 414` Changes the name and address of a student at index 2 to `John` and `Block 414` respectively. + +#### Delete a Student Profile + +Deletes the student's profile, given the index of the student. + +Format: `delete [index/STUDENT_INDEX]` + Examples: -* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` -* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` +* `delete index/1` Deletes the first profile in the student list. -### Listing all persons : `list` +
-Shows a list of all persons in the address book. +### Homework Commands +#### Assign Homework to a Student -Format: `list` +Create a homework assignment with a deadline for one or more students. + +Format: `new-homework [name/STUDENT_NAME]... [homework/HOMEWORK_NAME] [deadline/DEADLINE]` + +* The `STUDENT_NAME` must be an existing student of the tutor. +* You can enter multiple `name/` prefixes to assign the same homework to multiple students. +* The `DEADLINE` must be in the format given in the support date and time formats' appendix. +* The `DEADLINE` must be in the future. + +Examples: +* `new-homework name/John homework/listening comprehension ex1 deadline/2023-05-30 2359` adds the assignment `listening comprehension ex1` to the student named `John. The deadline is 30 May 2023 at 23:59. +* `new-homework name/Donald homework/english essay deadline/2023-05-14 2359` adds the assignment `English Essay` to the student named `Donald`. The deadline is 14 May 2023 at 23:59. +* `new-homework name/Kai Ze name/Muhammad homework/math ex1 deadline/2023-05-23 2359` adds the assignment `math ex1` to the students named `Kai Ze` and `Muhammad`. The deadline is 23 May 2023 at 23:59. + + +:bulb: **Tip:** You can use the `view-homework` command or click on the `Homework` button next to their name to view the list of homework the student currently has. + +:bulb: **Tip:** You can view the supported date and time formats [here](#supported-date-time-formats). + +:bulb: **Tip:** A student cannot have multiple homeworks with the same name, +even if they have different deadlines. + +:exclamation: **Caution:** STUDENT_NAME is case-insensitive and supports partial matching. +For example, `john` will match `John Doe` and `john doe`. You can refer to the [search by name mechanism](#search-by-name-mechanism) for more details. + +:exclamation: **Caution:** STUDENT_NAME should appear at least once and should not be empty. + +:exclamation: **Caution:** HOMEWORK_INDEX +and DEADLINE should all only appear exactly once and should not be empty. + + +
+ +#### View Student Homework + +Displays a list of homework with the ability to filter by student name and homework status. + +Format: `view-homework [name/STUDENT_NAME]... [status/STATUS]` -### Editing a person : `edit` +* By default, all homework will be displayed if no name or status parameter is provided. +* To view homework for specific students, specify the name using `name/STUDENT_NAME` prefixes. +* To view homework with a specific status, specify the status using `status/STATUS`. +* The available status values are `completed` and `pending`. +* It is possible to filter by both student name and status simultaneously. -Edits an existing person in the address book. +Examples: +* `view-homework` displays a list of all homework. +* `view-homework name/John` displays homework for a student named `John`. +* `view-homework status/completed` displays all completed homework from all students. +* `view-homework name/John status/pending` displays pending homework for a student named `John`. + +![View Homework](images/view-homework.jpg) + +:exclamation: **Caution:** STUDENT_NAME is case-insensitive and supports partial matching. +For example, `john` will match `John Doe` and `john doe`. You can refer to the [search by name mechanism](#search-by-name-mechanism) for more details. + +:exclamation: **Caution:** STATUS should only appear at most once. +STUDENT_NAME can appear zero or multiple times. + +
+ +#### Delete Homework from a Student + +Deletes a homework assignment for a student. -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +Format: `delete-homework [name/STUDENT_NAME] [index/HOMEWORK_INDEX]` -* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …​ -* At least one of the optional fields must be provided. -* Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person’s tags by typing `t/` without - specifying any tags after it. +* The `STUDENT_NAME` must be an existing student of the tutor. Note that there can only be one student's name. +* The `HOMEWORK_INDEX` must be the index of an existing homework assignment for the specified student. +* A success message will be displayed if the homework assignment is successfully deleted. Otherwise, an error message will be displayed. Examples: -* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively. -* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. -### Locating persons by name: `find` +* `delete-homework name/John index/1` deletes the first homework for the student named John. +* `delete-homework name/Susan index/3` deletes the third homework for the student named Susan. -Finds persons whose names contain any of the given keywords. -Format: `find KEYWORD [MORE_KEYWORDS]` -* The search is case-insensitive. e.g `hans` will match `Hans` -* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` -* Only the name is searched. -* Only full words will be matched e.g. `Han` will not match `Hans` -* Persons matching at least one keyword will be returned (i.e. `OR` search). - e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` +:exclamation: **Caution:** STUDENT_NAME is case-insensitive and supports partial matching. +For example, `john` will match `John Doe` and `john doe`. You can refer to the [search by name mechanism](#search-by-name-mechanism) for more details. + +:exclamation: **Caution:** STUDENT_NAME, and HOMEWORK_INDEX should all only appear exactly once. + +#### Mark the Homework of a Student as Done + +Marks homework of a student as done. + +Format: `mark-homework [name/STUDENT_NAME] [index/HOMEWORK_INDEX]` + +* The `STUDENT_NAME` must be an existing student of the tutor. +* The `HOMEWORK_INDEX` must be the index of an existing homework assignment for the specified student. +* A success message will be displayed if the homework assignment is successfully deleted. Otherwise, an error message will be displayed. Examples: -* `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) -### Deleting a person : `delete` +* `mark-homework name/John index/1` marks the first homework assignment for the student named John. +* `mark-homework name/Susan index/3` marks the third homework assignment for the student named Susan. + +![Mark Homework](images/mark-homework.jpg) -Deletes the specified person from the address book. +:exclamation: **Caution:** STUDENT_NAME is case-insensitive and supports partial matching. +For example, `john` will match `John Doe` and `john doe`. You can refer to the [search by name mechanism](#search-by-name-mechanism) for more details. -Format: `delete INDEX` +:exclamation: **Caution:** STUDENT_NAME, and HOMEWORK_INDEX should all only appear exactly once. -* Deletes the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. -* The index **must be a positive integer** 1, 2, 3, …​ +#### Unmark Homework of a Student as Undone + +Marks homework of a student as undone. + +Format: `unmark-homework [name/STUDENT_NAME] [index/HOMEWORK_INDEX]` + +* The `STUDENT_NAME` must be an existing student of the tutor. +* The `HOMEWORK_INDEX` must be the index of an existing homework assignment for the specified student. +* A success message will be displayed if the homework assignment is successfully deleted. Otherwise, an error message will be displayed. Examples: -* `list` followed by `delete 2` deletes the 2nd person in the address book. -* `find Betsy` followed by `delete 1` deletes the 1st person in the results of the `find` command. -### Clearing all entries : `clear` +* `unmark-homework name/John index/1`unmarks the first homework assignment for the student named John. +* `unmark-homework name/Susan index/3` unmarks the third homework assignment for the student named Susan. -Clears all entries from the address book. +![Unmark Homework](images/unmark-homework.jpg) -Format: `clear` +:exclamation: **Caution:** STUDENT_NAME is case-insensitive and supports partial matching. +For example, `john` will match `John Doe` and `john doe`. You can refer to the [search by name mechanism](#search-by-name-mechanism) for more details. -### Exiting the program : `exit` +:exclamation: **Caution:** STUDENT_NAME, and HOMEWORK_INDEX should all only appear exactly once. -Exits the program. +#### Update Homework of a Student -Format: `exit` +Updates the information on homework of a student + +Format: `update-homework [name/STUDENT_NAME] [index/HOMEWORK_INDEX] [homework/HOMEWORK_NAME] [deadline/DEADLINE]` + +* The `STUDENT_NAME` must be an existing student of the tutor. Note that there can only be one student's name. +* The `HOMEWORK_INDEX` must be the index of an existing homework assignment for the specified student. +* The `DEADLINE` must be in the format given in the support date and time formats' appendix. +* The `DEADLINE` must be in the future. +* `HOMEWORK_NAME` and `DEADLINE` are the updated values for this homework. +* At least one of homework names and deadline must be in the command. They can't both be absent. +* A success message will be displayed if the homework assignment is successfully deleted. Otherwise, an error message will be displayed. + +Examples: + +* `update-homework name/John index/1 homework/Math Assignment 1` updates the name of homework 1 of John to be `Math Assignment 1`. +* `update-homework name/Susan index/3 deadline/2023-05-12 23:59` updates the deadline of homework 3 of Susan to be `2023-05-12 23:59`. +* `update-homework name/Donald index/2 homework/Math Assignment 1 deadline/2023-05-12 23:59` updates the name of homework 2 of Donald to be `Math Assignment 1` and updates the deadline of homework 2 of Donald to be `2023-05-12 23:59`. + + +:bulb: **Tip:** You can use the `view-homework` command to view the list of homework the student currently has. + +:bulb: **Tip:** You can view the supported date and time formats [here](#supported-date-time-formats). -### Saving the data +:exclamation: **Caution:** STUDENT_NAME is case-insensitive and supports partial matching. +For example, `john` will match `John Doe` and `john doe`. You can refer to the [search by name mechanism](#search-by-name-mechanism) for more details. -AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +:exclamation: **Caution:** STUDENT_NAME, HOMEWORK_INDEX, +and DEADLINE should all only appear at most once. -### Editing the data file +:bulb: **Tip:** A student cannot have multiple homework with the same name, +even if they have different deadlines. -AddressBook data are saved as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. +
-
:exclamation: **Caution:** -If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run. +### Lessons Commands + +#### Create a New Lesson Plan for an Upcoming Lesson + +Creates a new lesson for a given student, with a lesson title and time. + +Format: `new-lesson [name/STUDENT_NAME] [lesson/LESSON_TITLE] [start/START_TIME] [end/END_TIME]` + +* The `STUDENT_NAME` must be an existing student of the tutor. +* `START_TIME` must be before `END_TIME`, and their difference must be at least 30 minutes and at most 3 hours. +* `START_TIME` and `END_TIME` must be in the future. +* A success message will be displayed if the lesson is successfully created. Otherwise, an error message will be displayed. + +Examples: +* `new-lesson name/John Doe lesson/The Water Cycle start/2025-03-23 1300 end/2025-03-23 1500` creates a new lesson for the student named `John Doe` with the lesson title `The Water Cycle` starting at `23 Mar 2025 13:00` and ending at `23 Mar 2025 15:00`. +* `new-lesson name/David Li name/John Doe lesson/Metamorphic Rocks start/2025-04-23 1300 end/2025-04-23 1500` creates a new lesson for the students named `David Li` and `John Doe` with the lesson title `Metamorphic Rocks` starting at `23 Apr 2025 13:00` and ending at `23 Apr 2025 15:00`. + + +:bulb: **Tip:** You can use the `view-lesson` command to view the list of lessons the student currently has. + +:bulb: **Tip:** You can view the supported date and time formats [here](#supported-date-time-formats). + +:exclamation: **Caution:** STUDENT_NAME is case-insensitive and supports partial matching. +For example, `john` will match `John Doe` and `john doe`. You can refer to the [search by name mechanism](#search-by-name-mechanism) for more details. + +:exclamation: **Caution:** STUDENT_NAME should appear exactly once. + +:exclamation: **Caution:** LESSON_TITLE, START_TIME, and END_TIME should all appear exactly once. + +:exclamation: **Caution:** A student can have multiple lessons with the same lesson title, provided that they do not overlap in time. + +
+:information_source: **Note:** Why do TutorPro allows adding homework and exams to multiple students in one command, but only allows adding lessons to one student at a time?
+This is because TutorPro assume all lessons you have with a student are one-to-one. Therefore, it is not possible for you to plan a single lesson with multiple students at the same time. +However, on the other hand, it is perfectly possible for you to assign the same homework to multiple students at the same time. It is also possible for multiple student to attend to the same exam at the same time.
-### Archiving data files `[coming in v2.0]` +#### View Lessons + +Displays the lessons for a given student/all students. + +Format: `view-lesson [name/STUDENT_NAME] [lesson/LESSON] [date/DATE] [done/DONE]` + +* Every parameter is optional. +* If no parameters are specified, the lessons for all students will be displayed. +* To view the lessons for specific students, specify the names using `name/STUDENT_NAME`s. +* To view the lessons whose titles contain a certain keyword/phrase, specify the keyword/phrase using `lesson/LESSON`. +* To view the lessons for a specific date, specify the date using `date/DATE`. +* To view the lessons that have been completed, include `done/done`. +* To view the lessons that haven't been completed, include `done/not done`. + +Examples: +* `view-lesson` Displays the lesson history for all the tutor’s students. +* `view-lesson name/John` Displays the lesson history for the student named John. +* `view-lesson name/John lesson/Math date/2023-05-03` Displays the lessons for student John, where the lessons' titles contain the keyword "Math", on the day 2023-05-03. +* `view-lesson done/done` Displays all lessons that'd been completed +* `view-lesson done/not done` Displays all lessons that haven't been completed +* `view-lesson name/John done/done` Displays all lessons that'd been completed for student John +* `view-lesson name/John name/Bernice done/not done` Displays all lessons that haven't been completed for students John and Bernice + +![View Lesson](images/view-lesson.jpg) + +:bulb: **Tip:** You can view the supported date formats [here](#supported-date-formats). + +:exclamation: **Caution:** STUDENT_NAME is case-insensitive and supports partial matching. +For example, `john` will match `John Doe` and `john doe`. You can refer to the [search by name mechanism](#search-by-name-mechanism) for more details. + +:exclamation: **Caution:** LESSON, DATE, and DONE can appear at most once. +STUDENT_NAME can appear multiple times. + +#### Delete a Lesson from a student +Deletes a lesson for a given student. + +Format: `delete-lesson [name/STUDENT_NAME] [index/LESSON_INDEX]` + +* The `STUDENT_NAME` must be an existing student of the tutor. Note that there can only be one student's name. +* The `LESSON_INDEX` must be a positive integer that is within the range of the student's lesson list. +* A success message will be displayed if the lesson is successfully deleted. Otherwise, an error message will be displayed. + +Example: +* `delete-lesson name/John Doe index/1` deletes the first lesson for the student named John Doe. +* `delete-lesson name/Bernice Yu index/2` deletes the second lesson for the student named Bernice Yu. + + +:bulb: **Tip:** You can use the `view-lesson` command to view the list of lessons the student currently has. + +:exclamation: **Caution:** STUDENT_NAME is case-insensitive and supports partial matching. +For example, `john` will match `John Doe` and `john doe`. You can refer to the [search by name mechanism](#search-by-name-mechanism) for more details. + +:exclamation: **Caution:** STUDENT_NAME and LESSON_INDEX should appear exactly once. + + +#### Update a Lesson +Updates a lesson for a given student. This can change the lesson title, start time, and/or end time. + +Format: `update-lesson [name/STUDENT_NAME] [index/LESSON_INDEX] [lesson/LESSON_TITLE] [start/START_TIME] [end/END_TIME]` + +* The `STUDENT_NAME` must be an existing student of the tutor. Note that there can only be one student's name. +* The `LESSON_INDEX` must be a positive integer that is within the range of the student's lesson list. +* `LESSON_TITLE`, `START_TIME` and `END_TIME` are the updated values for this lesson. At least one of them should be present. +* The provided `START_TIME` must be before the provided `END_TIME`, or, if the `END_TIME` is not provided, it must be before the original end time of the lesson. +* The provided `END_TIME` must be after the provided `START_TIME`, or, if the `START_TIME` is not provided, it must be after the original start time of the lesson. +* The updated lesson's duration must be at least 30 minutes and at most 3 hours. +* A success message will be displayed if the lesson is successfully updated. Otherwise, an error message will be displayed. + +Example: +* `update-lesson name/John Doe index/1 lesson/The Water Cycle start/2025-03-23 1300 end/2025-03-23 1500` updates the first lesson for the student named John Doe to have the lesson title `The Water Cycle`, start time `23 Mar 2025 13:00`, and end time `23 Mar 2025 15:00`. +* `update-lesson name/Bernice Yu index/2 lesson/Photosynthesis` updates the second lesson for the student named Bernice Yu to have the lesson title `Photosynthesis`. +* `update-lesson name/John Doe index/1 start/2025-03-23 1300` updates the first lesson for the student named John Doe to have the start time `23 Mar 2025 13:00`. +* `update-lesson name/Bernice Yu index/2 end/2025-03-23 1500` updates the second lesson for the student named Bernice Yu to have the end time `23 Mar 2025 15:00`. + + +:bulb: **Tip:** You can use the `view-lesson` command to view the list of lessons the student currently has. + +:bulb: **Tip:** You can view the supported date and time formats [here](#supported-date-time-formats). + +:exclamation: **Caution:** STUDENT_NAME is case-insensitive and supports partial matching. +For example, `john` will match `John Doe` and `john doe`. You can refer to the [search by name mechanism](#search-by-name-mechanism) for more details. + +:exclamation: **Caution:** STUDENT_NAME, LESSON_INDEX should appear exactly once. + +:exclamation: **Caution:** LESSON_TITLE, START_TIME, and END_TIME should each appear at most once. + +
+ +### Exam Commands + +#### Add an Exam to be tracked + +Create an Exam for a given student(s). + +Format: `new-exam [name/STUDENT_NAME].. [exam/EXAM_NAME] [start/START_TIME] +[end/END_TIME] [weightage/WEIGHTAGE] [grade/GRADE]` + +* One or more `STUDENT_NAME` prefixes must be provided. Multiple `STUDENT_NAME` will assign the exam to multiple students. +* `GRADE` and `WEIGHTAGE` are optional. +* The format of `GRADE` should be `grade/ACTUAL_SCORE/TOTAL_SCORE` +* `GRADE` can only be saved if the exam is already completed. +* `WEIGHTAGE` should be entered as a percentage out of 100 (with/without the % symbol). + +Examples: +* `new-exam name/John Doe exam/Math MYE start/2023-05-21 12:00 end/2023-05-21 14:00` +* `new-exam name/John Doe name/Faye Doe exam/Science MYE start/2023-05-22 12:00 end/2023-05-22 14:00` + +:bulb: **Tip:** You can view the supported date and time formats [here](#supported-date-time-formats). + +:exclamation: **Caution:** STUDENT_NAME is case-insensitive and supports partial matching. +For example, `john` will match `John Doe` and `john doe`. +You can refer to the [search by name mechanism](#search-by-name-mechanism) for more details. + +:exclamation: **Caution:** STUDENT_NAME should appear at least once. -_Details coming soon ..._ +:exclamation: **Caution:** EXAM_NAME, START_TIME, and END_TIME should appear exactly once. + +#### Remove an exam + +Deletes an exam of a student. + +Format: `delete-exam [name/STUDENT_NAME] [index/EXAM_INDEX]` + +* Removes an exam that TutorPro is currently tracking. +* At least one student name must be provided. +* `EXAM_INDEX` is in reference to the indexing of the exams listed when invoking the `view-exam` command on a + student. +* There can be multiple `STUDENT_NAME`s provided to this command, and each name provided will attempt to match with only + one student. eg. `delete-exam name/John name/Faye index/1` will attempt to match each name to a student being tracked, + and will result in exams of index '1' of students 'John' and 'Faye' being removed from TutorPro. + +Examples: +* `delete-exam name/John Doe index/1` - This command will remove student John's 1st indexed exam. + +:bulb: **Tip:** You can use the `view-exam` command to view the list of exams the student currently has. + +:exclamation: **Caution:** STUDENT_NAME is case-insensitive and supports partial matching. +For example, `john` will match `John Doe` and `john doe`. +You can refer to the [search by name mechanism](#search-by-name-mechanism) for more details. + +:exclamation: **Caution:** STUDENT_NAME should appear at least once. + +#### View exams tracked by TutorPro + +Displays exams stored in TutorPro, with the option to filter based on Exam date, Student name or past/upcoming status. + +Format: `view-exam [name/STUDENT_NAME].. [date/DATE] [exam/NAME_OF_EXAM] [done/IS_DONE]` + +* Lists exams TutorPro is currently tracking, while filtering for the specified predicates +* All predicates are optional, leaving all parameters blank will list all currently tracked exams +* Field `IS_DONE` can be used with values `done` to show completed exams, or `not done` to show upcoming exams. +* There can be multiple `STUDENT_NAME` prefixes provided to this command, and each name provided will attempt to match with only + one student. eg. `view-exam name/John name/Faye` will attempt to match each name to a student being tracked, and will + result in exams of students 'John' and 'Faye' being listed. + + +Examples: +* `view-exam` - Lists all exams currently being tracked by TutorPro +* `view-exam name/John date/2023-05-01 exam/MYE done/` - List exams attributed to student 'John' on date '2023-05-01' + with description 'MYE' which are undone. + +![view-exam.png](images/view-exam.png) + +:bulb: **Tip:** You can view the supported date formats [here](#supported-date-formats). + +:exclamation: **Caution:** STUDENT_NAME is case-insensitive and supports partial matching. +For example, `john` will match `John Doe` and `john doe`. +You can refer to the [search by name mechanism](#search-by-name-mechanism) for more details. + +:exclamation: **Caution:** The only parameter allowed to have more than one value is `STUDENT_NAME`. + +#### Edit exam details + +Updates an exam's information. + +Format: `update-exam [name/STUDENT_NAME] [index/EXAM_INDEX] [exam/NEW_EXAM_NAME] [start/START_TIME] +[end/END_TIME] [weightage/WEIGHTAGE] [grade/GRADE]` + +* Updates the details of an exam tracked by TutorPro. +* `NEW_EXAM_NAME`, `START_TIME`, `END_TIME`, `WEIGHTAGE` and `GRADE` are the the updated values for this exam and are optional. +* Of the optional fields, at least one must be provided in order to update the exam. +* `GRADE` can only be updated after the exam is already completed. +* `EXAM_INDEX` is in reference to the indexing of the exams listed when invoking the `view-exam` command on a + student. + +Examples: +* `update-exam name/John index/1 grade/20/25` -updates the first exam (index when `view-exam` is invoked with student + name) grade to `20/25`. + +:bulb: **Tip:** You can use the `view-exam` command to view the list of exams the student currently has. + +:exclamation: **Caution:** STUDENT_NAME is case-insensitive and supports partial matching. +For example, `john` will match `John Doe` and `john doe`. +You can refer to the [search by name mechanism](#search-by-name-mechanism) for more details. + +:exclamation: **Caution:** STUDENT_NAME should appear exactly once and should not be empty. + + +
+ +### Global Commands + +#### Get Help for TutorPro + +Shows the link for TutorPro support and documentation. + +Format: `help` + +* Opens the help window where you can copy the link to TutorPro's support site. + +Example: +* `help` Opens the help window. + +#### Clear All TutorPro Data + +Deletes all information stored in TutorPro. + +Format: `clear` + +Example: +* `clear` Deletes all student, homework, lesson and exam data in TutorPro. + +:exclamation: **Caution:** Clear is an irreversible command. Any data you lose cannot be recovered anymore. Please use with caution. + + +#### Exit TutorPro + +Closes TutorPro. + +Format: `exit` + +* This command is just a quick shortcut for keyboard-savvy users. Closing TutorPro with the (X) icon also safely quits TutorPro, without losing any data. + +Example: +* `exit` Safely closes TutorPro. + +
+ +## Unique Mechanisms + +### Search by Name Mechanism + +* TutorPro uses students' Names as primary keys to identify students. +* Most commands in TutorPro allow you to search for a student by name, rather than by index, which is more intuitive for the user and eliminates the need to remember the index of the student. +* Therefore, duplicate names aren't allowed. Names that are substrings of other names or vice versa aren't allowed. For example, `John Doe` and `John` are not allowed. If you have students with the exact name, say `John Doe`, you can add a number to the end of the name to differentiate them. For example, `John Doe 1` and `John Doe 2`. +* The search by name mechanism is case-insensitive, meaning that the search will be case-insensitive. For Example, `john doe` and `John Doe` will be treated as the same name. +* Partial names can be used as well. For example, `doe` will return all students with the name `John Doe` and `Jane Doe`. + +### Schedule Clash Detection Mechanism + +#### Schedule Clash Detection Mechanism when Adding a New Lesson + +##### With respect to existing lessons +* TutorPro will detect if there is a clash between the new lesson and existing lessons of all students. +* Since TutorPro is meant for one tutor, it is assumed that the tutor will not be teaching two lessons at the same time. +* When adding a new lesson, TutorPro will check if the lesson clashes with any other lessons of all students. For example: + * Running command `new-lesson name/John Doe lesson/Math Lesson start/2023-05-21 12:00 end/2023-05-21 14:00` will add a new lesson for `John Doe` on `2023-05-21` from `12:00` to `14:00`. + * If you then run command `new-lesson name/John Doe lesson/Science Lesson start/2023-05-21 13:00 end/2023-05-21 15:00`, which adds a new lesson for `John Doe` on `2023-05-21` from `13:00` to `15:00`, TutorPro will detect that there is a clash in the schedule and will not add the lesson for `John Doe` as a student can't have two lessons at the same time. +* We also Assume that tutors will only teach one student at a time. Therefore, if multiple students have lessons even with the same at the same time, TutorPro will detect that there is a clash in the schedule and will not add the lesson for the student. For example: + * Running command `new-lesson name/John Doe lesson/Math Lesson start/2023-05-21 12:00 end/2023-05-21 14:00` will add a new lesson for `John Doe` on `2023-05-21` from `12:00` to `14:00`. + * If you then run command `new-lesson name/Irfan Ibrahim lesson/Math Lesson start/2023-05-21 12:00 end/2023-05-21 14:00`, which adds a new lesson for `Irfan Ibrahim` on `2023-05-21` from `12:00` to `14:00`, TutorPro will detect that there is a clash in the schedule and will not add the lesson for `Irfan Ibrahim` as a tutor can't teach two students at the same time. + +##### With respect to existing exams +* TutorPro will detect if there is a clash between the new lesson and existing exams of the particular student. +* Since the timing of the exam is determined by the school of the student, TutorPro will prioritize the timing of the exam over the new lesson. +* We assume that a student will not have an exam and a lesson at the same time. For example: + * Run command `new-exam name/John Doe exam/Math Exam start/2023-05-21 13:00 end/2023-05-21 15:00`, which adds a new exam for `John Doe` on `2023-05-21` from `13:00` to `15:00`, + * If you then run command `new-lesson name/John Doe lesson/Math Lesson start/2023-05-21 12:00 end/2023-05-21 14:00`, which will add a new lesson for `John Doe` on `2023-05-21` from `12:00` to `14:00`, TutorPro will detect that there is a clash in the schedule and will not add the lesson for `John Doe`, as a student can't have an exam and a lesson at the same time. + +#### Schedule Clash Detection Mechanism when Adding a New Exam + +##### With respect to existing lessons +* TutorPro will detect if there is a clash between the new exam and existing lessons of the particular student. +* Since the timing of the exam is determined by the school of the student, TutorPro will prioritize the timing of the new exam over the existing lessons. +* We assume that a student will not have an exam and a lesson at the same time. For example: + * Run command `new-lesson name/John Doe lesson/Math Lesson start/2023-05-21 12:00 end/2023-05-21 14:00` will add a new lesson for `John Doe` on `2023-05-21` from `12:00` to `14:00`. + * If you then run command `new-exam name/John Doe exam/Science Exam start/2023-05-21 13:00 end/2023-05-21 15:00`, which adds a new exam for `John Doe` on `2023-05-21` from `13:00` to `15:00`, unlike the previous section, TutorPro will allow you to add the exam for `John Doe` as the exam timing is more important than the lesson timing. + * However, TutorPro will detect that the clash in the schedule and will prompt you to update the clashed lesson's timing. + +##### With respect to existing exams +* TutorPro will detect if there is a clash in the schedule of a student when adding a new exam. +* We assume that a student will not have two exams at the same time. +* Unlike adding a new lesson, when adding a new exam, TutorPro will check if the exam clashes with any other exams of the same student only. (note the difference between adding a new lesson and adding a new exam). For example: + * Run command `new-exam name/John Doe exam/English Exam start/2023-05-21 12:00 end/2023-05-21 14:00` will add a new exam for `John Doe` on `2023-05-21` from `12:00` to `14:00`. + * If you run command `new-exam name/John Doe exam/Math Exam start/2023-05-21 13:00 end/2023-05-21 14:00`, which adds a new exam for `John Doe` on `2023-05-21` from `13:00` to `14:00`, TutorPro will detect that there is a clash in the schedule and will not add the exam for `John Doe` as a student can't have two exams at the same time. + +
+ +### Duplicate Detection Mechanism +TutorPro will detect duplicate homeworks, +lessons and exams and will not add them to the student's list of homeworks, lessons and exams respectively. +However, +you may have noticed that duplicate homework name is not allowed but duplicate lesson name and exam name is allowed. +This is a carefully crafted feature that will be explained in the following section. + +#### Duplicate Homework Detection +* TutorPro identifies homework by its name, since we assume that a student can have multiple homeworks with the same deadline. +* Since name is the primary key for homework, duplicate homework names are not allowed. For example, if you have a homework named `Math Homework` and you try to add another homework with the same name even with a different deadline, TutorPro will detect that there is a duplicate homework and will not add the homework. +* If you have homeworks with the same name, we encourage you to add a number to the end of the name to differentiate them. For example, `Math Homework 1` and `Math Homework 2`. + +#### Duplicate Lesson Detection +* TutorPro identifies a lesson by its start time and end time, since we assume that a student can have multiple lessons with the same name, but they can't have two lessons at the same time. +* Since start time and end time is the primary key for lessons, lessons that `clash` with other lessons aren't allowed. (See [Schedule Clash Detection Mechanism when Adding a New Lesson](#schedule-clash-detection-mechanism-when-adding-a-new-lesson) for more details) +* However, adding lessons with the duplicate name but without `clashes` with other lessons are allowed. For example: + * if you have a lesson named `Math Lesson` for `John Doe` on `2023-05-21` from `12:00` to `14:00` + * you then try to add another lesson with the same name but on `2023-05-24` from `13:00` to `15:00`, TutorPro will not detect that there is a duplicate lesson and will add the lesson. + + +
+ +#### Duplicate Exam Detection +* TutorPro identifies an exam by its start time and end time, since we assume that a student can have multiple exams with the same name, but they can't have two exams at the same time. +* Since start time and end time is the primary key for exams, exams that `clash` with other exams aren't allowed. (See [Schedule Clash Detection Mechanism when Adding a New Exam](#schedule-clash-detection-mechanism-when-adding-a-new-exam) for more details) +* However, adding exams with the duplicate name but without `clashes` with other exams are allowed. For example: + * if you have an exam named `Math Exam` for `John Doe` on `2023-05-21` from `12:00` to `14:00` + * you then try to add another exam with the same name but on `2023-05-24` from `13:00` to `15:00`, TutorPro will not detect that there is a duplicate exam and will add the exam. + +### Storage Mechanism +:biohazard: This section is for advanced users who are interested in learning about the storage mechanism of TutorPro. If you are new to TutorPro, we recommend that you skip this section and do not edit the JSON file directly. + +* TutorPro stores all the data in a JSON file. +* The JSON file is located in the `data` folder of the TutorPro home folder. +* If it is your first time using TutorPro, the TutorPro home folder will be created in the same directory as the TutorPro JAR file and a sample student list will be created for your to quickly get started. +* We discourage you from editing the JSON file directly as the content you edited may not be in the correct format and may not be able to be parsed by TutorPro. +* If the storage file is corrupted, TutorPro will create a new storage file and an empty student list. + +:warning: **Warning**
+If what you edit in the JSON file is in the correct format but violates the constraint of TutorPro, TutorPro can parse the data file but may not be able to function as expected. For example, if you edit the end time of an exam to be earlier than the start time of the exam. +Therefore, we strongly discourage you from editing the JSON file directly unless you are absolutely sure of what you are doing. -------------------------------------------------------------------------------------------------------------------- +
+ ## FAQ -**Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder. +**Q**: How can I transfer my data to another computer in TutorPro?
+**A**: You can transfer your data to another computer by installing TutorPro on the new computer and replacing its empty data file with the one that contains the data from your previous TutorPro home folder. Refer to [Storage Mechanism](#storage-mechanism) for more details. + +**Q**: My students' full information is not shown on the student list. +How can I view the full information of a student? +
+**A**: You can view the full information of a student by running command `view-student [index/INDEX]`. +For example, if you want to view the full information of the student at index 1, +you can run command `view-student index/1`. +Alternatively, you can also view with the profile button on the student card. -------------------------------------------------------------------------------------------------------------------- -## Command summary - -Action | Format, Examples ---------|------------------ -**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​`
e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` -**Clear** | `clear` -**Delete** | `delete INDEX`
e.g., `delete 3` -**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…​`
e.g.,`edit 2 n/James Lee e/jameslee@example.com` -**Find** | `find KEYWORD [MORE_KEYWORDS]`
e.g., `find James Jake` -**List** | `list` -**Help** | `help` +
+ +## Summary + +### List of Commands + +| Action | Command Format | Example | +|:-----------------------------|------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------| +| Create new student profile | `new-student [name/STUDENT_NAME] [address/STUDENT_ADDRESS] [phone/PHONE] [email/EMAIL] [school/SCHOOL] [level/GRADE_LEVEL]` | `new-student name/John Doe address/21 Prince George’s Park email/jdoe@gmail.com phone/12345678 school/ACJC level/sec8` | +| List all students | `list` | `list` | +| Update student information | `update-info [index/INDEX] [name/STUDENT_NAME] [field/NEW_INFO]...` | `update-info index/1 name/John address/Block 123 #12-34` | +| Delete student profile | `delete [index/STUDENT_INDEX]` | `delete index/1` | +| View Profile | `view-profile [name/STUDENTS_NAME]` | `view-profile name/John` | +| Assign homework to a student | `new-homework [name/STUDENT_NAME]... [homework/HOMEWORK_NAME] [deadline/DEADLINE]` | `new-homework name/John homework/listening comprehension ex1 deadline/02-12-2023 23:59` | +| View student's homework | `view-homework [name/STUDENT_NAME]... [status/STATUS]` | `view-homework name/John status/pending` | +| Delete student's homework | `delete-homework [name/STUDENT_NAME] [index/HOMEWORK_INDEX]` | `delete-homework name/John index/1` | +| Mark homework as done | `mark-homework [name/STUDENT_NAME] [index/HOMEWORK_INDEX]` | `mark-homework name/John index/1` | +| Unmark homework as undone | `unmark-homework [name/STUDENT_NAME] [index/HOMEWORK_INDEX]` | `unmark-homework name/John index/1` | +| Update student's homework | `update-homework [name/STUDENT_NAME] [index/HOMEWORK_INDEX] [homework/HOMEWORK_NAME] [deadline/DEADLINE]` | `update-homework name/John index/1 homework/Math Assignment 1` | +| Create new lesson plan | `new-lesson [name/STUDENT_NAME] [lesson/LESSON_TITLE] [start/START_TIME] [end/END_TIME]` | `new-lesson name/John Doe lesson/The Water Cycle start/25-03-23-1300 end/25-03-23-1500` | +| View lessons history | `view-lesson [name/STUDENT_NAME] [subject/SUBJECT] [date/DATE] [done/DONE]` | `view-lesson name/John` | +| Delete a lesson | `delete-lesson [name/STUDENT_NAME] [index/LESSON_INDEX]` | `delete-lesson name/John Doe index/1` | +| Update a lesson | `update-lesson [name/STUDENT_NAME] [index/LESSON_INDEX] [lesson/LESSON_TITLE] [start/START_TIME] [end/END_TIME]` | `update-lesson name/John Doe index/1 lesson/The Water Cycle start/2025-03-23 1300 end/2025-03-23 1500` | +| Add an exam | `new-exam [name/STUDENT_NAME]... [exam/EXAM_NAME] [start/START_TIME] [end/END_TIME] [weightage/WEIGHTAGE] [grade/GRADE]` | `new-exam name/John Doe exam/Math MYE start/2023-05-21 12:00 end/2023-05-21 14:00` | +| Remove an exam | `delete-exam [name/STUDENT_NAME]... [index/INDEX_OF_EXAM]` | `delete-exam name/John Doe index/1` | +| Update an exam | `update-exam [name/STUDENT_NAME] [index/INDEX_OF_EXAM] [exam/NEW_EXAM_NAME] [start/START_TIME] [end/END_TIME] [grade/GRADE]` | `update-exam name/John Doe index/1 exam/Math MYE` | +| View exams | `view-exam [name/STUDENT_NAME]... [date/DATE] [exam/EXAM_NAME] [done/DONE_STATUS]` | `view-exam name/John Doe date/2023-05-01 exam/MYE done/` | +| Get help for TutorPro | `help` | `help` | +| Exit TutorPro | `exit` | `exit` | + +
+ +### List of Prefixes + +| Prefix | Meaning | Usage | Example | +|:-------------|-------------|-----------------------------------------------------|-----------------------------------| +| `name/` | Name | Student name | `name/John Doe` | +| `phone/` | Phone | Phone number of a Student | `phone/12345678` | +| `email/` | Email | Email address of a Student | `email/johndoe@gmail.com` | +| `address/` | Address | Home address of a Student | `address/21 Prince George's Park` | +| `level/` | Grade Level | Grade level of a Student | `level/sec8` | +| `school/` | School | School name of a Student | `school/ACJC` | +| `tag/` | Tag | Tag on a Student | `tag/favorite` | +| `homework/` | Homework | name of Homework assigned to a Student | `homework/Math Assignment` | +| `deadline/` | Deadline | Due date | `deadline/02-12-2023 2359` | +| `exam/` | Exam | Exam name | `exam/Math MYE` | +| `status/` | Status | Indicates whether a homework is completed | `status/pending` | +| `index/` | Index | Index of a student/homework/lesson/exam | `index/1` | +| `lesson/` | Lesson | Lesson title | `lesson/The Water Cycle` | +| `start/` | Start Time | Start time of a lesson/exam | `start/2025-03-23 1300` | +| `end/` | End Time | End time of a lesson/exam | `end/2025-03-23 1500` | +| `date/` | Date | Date of a lesson/exam | `date/2023-03-29` | +| `done/` | Done | indicates if a lesson/exam is past the current time | `done/done` | + + + +### Supported date-time formats +* `MMM dd yyyy HHmm` +* `MMM dd yyyy HH:mm` +* `yyyy-MM-dd'T'HH:mm ` +* `dd/MM/yyyy HHmm` +* `dd/MM/yyyy HH:mm` +* `yyyy/MM/dd HHmm` +* `yyyy/MM/dd HH:mm ` +* `yyyy/MM/dd'T'HHmm` +* `yyyy/MM/dd'T'HH:mm ` +* `yyyy-MM-dd HHmm` +* `yyyy-MM-dd HH:mm ` +* `dd MMM yyyy HHmm` +* `dd MMM yyyy HH:mm ` +* `MMM dd, yyyy HHmm` +* `MMM dd, yyyy HH:mm ` + +### Supported date formats +* `MMM dd yyyy` +* `yyyy-MM-dd` +* `dd/MM/yyyy` +* `yyyy/MM/dd` +* `dd MMM yyyy` +* `MMM dd, yyyy` +* `dd-mm-yyyy` diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..7ff28f42471 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "TutorPro" theme: minima header_pages: @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "AY2223S2-CS2103T-W13-4/tp" github_icon: "images/github-icon.png" plugins: diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss index 0d3f6e80ced..9cc4e2c2386 100644 --- a/docs/_sass/minima/_base.scss +++ b/docs/_sass/minima/_base.scss @@ -288,7 +288,7 @@ table { text-align: center; } .site-header:before { - content: "AB-3"; + content: "TutorPro"; font-size: 32px; } } diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index ef81d18c337..8ecd8898c14 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -10,9 +10,10 @@ Participant ":Storage" as storage STORAGE_COLOR user -[USER_COLOR]> ui : "delete 1" activate ui UI_COLOR -ui -[UI_COLOR]> logic : execute("delete 1") +ui -[UI_COLOR]> logic : execute("delete index/1") activate logic LOGIC_COLOR + logic -[LOGIC_COLOR]> model : deletePerson(p) activate model MODEL_COLOR diff --git a/docs/diagrams/ClickButtonActivityDiagram.puml b/docs/diagrams/ClickButtonActivityDiagram.puml new file mode 100644 index 00000000000..fa262c8d6fe --- /dev/null +++ b/docs/diagrams/ClickButtonActivityDiagram.puml @@ -0,0 +1,29 @@ +@startuml +'https://plantuml.com/activity-diagram-beta + +start + +:User clicks a button; +:Student Card creates the Header Bar; +:Detailed Info Section sets the Header Bar; + +if () then ([ProfileButton]) +:Detailed Info Section creates Profile Content; +:Detailed Info Section sets the Profile Content; + +else() then ([HomeworkButton, LessonsButton, ExamsButton]) +if () then ([no homework, lessons or exams]) +:Detailed Info Section creates Empty Content; +:Detailed Info Section sets the Empty Content; +else ([Student has homework]) +:Detailed Info Section gets information about homework, lessons or exams; +:Detailed Info Section creates Filled Content; +:Detailed Info Section sets the Filled Content; +endif + +endif + +:Detailed Info Section sets the Detailed Content; +stop + +@enduml diff --git a/docs/diagrams/CreateLessonAD.puml b/docs/diagrams/CreateLessonAD.puml new file mode 100644 index 00000000000..3ba2dd6b08a --- /dev/null +++ b/docs/diagrams/CreateLessonAD.puml @@ -0,0 +1,12 @@ +@startuml +'https://plantuml.com/activity-diagram-beta + +start + +:User inputs new-lesson command; +:Instantiates a CreateLessonCommand; +:Creates a Lesson object according to user inputs; +:Adds the lesson to the specified Student's list of lessons; +:Update the UI to inform user the Lesson is successfully added; + +@enduml diff --git a/docs/diagrams/CreateLessonSequenceDiagram.puml b/docs/diagrams/CreateLessonSequenceDiagram.puml new file mode 100644 index 00000000000..086b8e4cda4 --- /dev/null +++ b/docs/diagrams/CreateLessonSequenceDiagram.puml @@ -0,0 +1,89 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":Logic Manager" as lm LOGIC_COLOR +participant ":AddressBookParser" as abp LOGIC_COLOR +participant ":CreateLessonCommandParser" as clp LOGIC_COLOR +participant "c:CreateLessonCommand" as clc LOGIC_COLOR +participant ":CommandResult" as cr LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Student" as s MODEL_COLOR +participant ":UniqueLessonList" as ll MODEL_COLOR +participant ":Lesson" as l MODEL_COLOR +end box + +[-> lm : execute("new-lesson\nname/John\nlesson/Math Lesson\nstart/2023-05-01 1200\nend/2023-05-01 1400") +activate lm + +lm -> abp : parseCommand("new-lesson\nname/John\nlesson/Math Lesson\nstart/2023-05-01 1200\nend/2023-05-01 1400") +activate abp + +create clp +abp -> clp +activate clp + +clp --> abp +deactivate clp + +abp -> clp : parse("new-lesson\nname/John\nlesson/Math Lesson\nstart/2023-05-01 1200\nend/2023-05-01 1400") +activate clp + +create clc +clp -> clc +activate clc + +clc --> clp : c +deactivate clc + +clp --> abp : c +deactivate clp +'Hidden arrow to position the destroy marker below the end of the activation bar. +clp -[hidden]-> abp +destroy clp + +abp --> lm : c +deactivate abp + +lm -> clc : execute() +activate clc + +create l +clc -> l +activate l + +l --> clc : l +deactivate l + +clc -> s : addLesson(l) +activate s + +s -> ll : addLesson(l) +activate ll + +ll --> s +deactivate ll + +s --> clc +deactivate s + +create cr +clc -> cr +activate cr + +cr --> clc +deactivate cr + +clc --> lm : result +deactivate clc + +[<--lm + + + + + + +@enduml diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 1dc2311b245..5f2730322fe 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -13,10 +13,10 @@ box Model MODEL_COLOR_T1 participant ":Model" as Model MODEL_COLOR end box -[-> LogicManager : execute("delete 1") +[-> LogicManager : execute("delete index/1") activate LogicManager -LogicManager -> AddressBookParser : parseCommand("delete 1") +LogicManager -> AddressBookParser : parseCommand("delete index/1") activate AddressBookParser create DeleteCommandParser diff --git a/docs/diagrams/ExamsClickSequenceDiagram.puml b/docs/diagrams/ExamsClickSequenceDiagram.puml new file mode 100644 index 00000000000..fa2776df1d0 --- /dev/null +++ b/docs/diagrams/ExamsClickSequenceDiagram.puml @@ -0,0 +1,71 @@ +@startuml +!include style.puml + +box Ui UI_COLOR_T1 +participant ":StudentLisCard" as StudentListCard UI_COLOR +participant ":MainWindow" as MainWindow UI_COLOR +participant ":DetailedInfoSection" as DetailedInfoSection UI_COLOR +participant ":FilledExamsContent" as FilledExamsContent UI_COLOR +participant ":EmptyExamsContent" as EmptyExamsContent UI_COLOR +end box + +[-> StudentListCard : handleViewLessonsButtonClick() +activate StudentListCard + +StudentListCard -> MainWindow : setDetailedHeaderBar(firstName) +activate MainWindow + +MainWindow -> DetailedInfoSection : setDetailedHeaderBar(firstName) +activate DetailedInfoSection + +DetailedInfoSection -> DetailedInfoSection : setHeaderBar(firstName) +activate DetailedInfoSection + + +DetailedInfoSection --> DetailedInfoSection +deactivate DetailedInfoSection + +DetailedInfoSection --> MainWindow +deactivate DetailedInfoSection + +MainWindow --> StudentListCard +deactivate MainWindow + +alt student has lessons + create FilledExamsContent + StudentListCard -> FilledExamsContent : new FilledExamsContent(student) + activate FilledExamsContent + + return FilledExamsContent + +else student has no lessons + create EmptyExamsContent + StudentListCard -> EmptyExamsContent : new EmptyExamsContent(student) + activate EmptyExamsContent + + return EmptyExamsContent + +end + +StudentListCard -> MainWindow : setDetailedContent() +activate MainWindow + +MainWindow -> DetailedInfoSection : setDetailedContent() +activate DetailedInfoSection + +DetailedInfoSection -> DetailedInfoSection : setContent() +activate DetailedInfoSection + +DetailedInfoSection --> DetailedInfoSection +deactivate DetailedInfoSection + +DetailedInfoSection --> MainWindow +deactivate DetailedInfoSection + +MainWindow --> StudentListCard +deactivate MainWindow + +[<-- StudentListCard +deactivate StudentListCard + +@enduml diff --git a/docs/diagrams/HomeworkClickSequenceDiagram.puml b/docs/diagrams/HomeworkClickSequenceDiagram.puml new file mode 100644 index 00000000000..d84a812d314 --- /dev/null +++ b/docs/diagrams/HomeworkClickSequenceDiagram.puml @@ -0,0 +1,69 @@ +@startuml +!include style.puml + +box Ui UI_COLOR_T1 +participant ":StudentLisCard" as StudentListCard UI_COLOR +participant ":MainWindow" as MainWindow UI_COLOR +participant ":DetailedInfoSection" as DetailedInfoSection UI_COLOR +participant ":FilledHomeworkContent" as FilledHomeworkContent UI_COLOR +participant ":EmptyHomeworkContent" as EmptyHomeworkContent UI_COLOR +end box + + +[-> StudentListCard : handleViewHomeworkButtonClick() +activate StudentListCard + +StudentListCard -> MainWindow : setDetailedHeaderBar(firstName) +activate MainWindow + +MainWindow -> DetailedInfoSection : setDetailedHeaderBar(firstName) +activate DetailedInfoSection + +DetailedInfoSection -> DetailedInfoSection : setHeaderBar(firstName) +activate DetailedInfoSection + +DetailedInfoSection --> DetailedInfoSection +deactivate DetailedInfoSection + +DetailedInfoSection --> MainWindow +deactivate DetailedInfoSection + +MainWindow --> StudentListCard +deactivate MainWindow + +alt student has homework + create FilledHomeworkContent + StudentListCard -> FilledHomeworkContent : new FilledHomeworkContent(student) + activate FilledHomeworkContent + return FilledHomeworkContent + +else student has no homework + create EmptyHomeworkContent + StudentListCard -> EmptyHomeworkContent : new EmptyHomeworkContent(student) + activate EmptyHomeworkContent + + return EmptyHomeworkContent +end + +StudentListCard -> MainWindow : setDetailedContent() +activate MainWindow + +MainWindow -> DetailedInfoSection : setDetailedContent() +activate DetailedInfoSection + +DetailedInfoSection -> DetailedInfoSection : setContent() +activate DetailedInfoSection + +DetailedInfoSection --> DetailedInfoSection +deactivate DetailedInfoSection + +DetailedInfoSection --> MainWindow +deactivate DetailedInfoSection + +MainWindow --> StudentListCard +deactivate MainWindow + +[<-- StudentListCard +deactivate StudentListCard + +@enduml diff --git a/docs/diagrams/LessonsClickSequenceDiagram.puml b/docs/diagrams/LessonsClickSequenceDiagram.puml new file mode 100644 index 00000000000..126ac035d8a --- /dev/null +++ b/docs/diagrams/LessonsClickSequenceDiagram.puml @@ -0,0 +1,71 @@ +@startuml +!include style.puml + +box Ui UI_COLOR_T1 +participant ":StudentLisCard" as StudentListCard UI_COLOR +participant ":MainWindow" as MainWindow UI_COLOR +participant ":DetailedInfoSection" as DetailedInfoSection UI_COLOR +participant ":FilledLessonsContent" as FilledLessonsContent UI_COLOR +participant ":EmptyLessonsContent" as EmptyLessonsContent UI_COLOR +end box + +[-> StudentListCard : handleViewLessonsButtonClick() +activate StudentListCard + + +StudentListCard -> MainWindow : setDetailedHeaderBar(firstName) +activate MainWindow + +MainWindow -> DetailedInfoSection : setDetailedHeaderBar(firstName) +activate DetailedInfoSection + +DetailedInfoSection -> DetailedInfoSection : setHeaderBar(firstName) +activate DetailedInfoSection + +DetailedInfoSection --> DetailedInfoSection +deactivate DetailedInfoSection + +DetailedInfoSection --> MainWindow +deactivate DetailedInfoSection + +MainWindow --> StudentListCard +deactivate MainWindow + +alt student has lessons + create FilledLessonsContent + StudentListCard -> FilledLessonsContent : new FilledLessonsContent(student) + activate FilledLessonsContent + + return FilledLessonsContent + +else student has no lessons + create EmptyLessonsContent + StudentListCard -> EmptyLessonsContent : new EmptyLessonsContent(student) + activate EmptyLessonsContent + + return EmptyLessonsContent + +end + +StudentListCard -> MainWindow : setDetailedContent() +activate MainWindow + +MainWindow -> DetailedInfoSection : setDetailedContent() +activate DetailedInfoSection + +DetailedInfoSection -> DetailedInfoSection : setContent() +activate DetailedInfoSection + +DetailedInfoSection --> DetailedInfoSection +deactivate DetailedInfoSection + +DetailedInfoSection --> MainWindow +deactivate DetailedInfoSection + +MainWindow --> StudentListCard +deactivate MainWindow + +[<-- StudentListCard +deactivate StudentListCard + +@enduml diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 4439108973a..625712f66de 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -6,45 +6,67 @@ skinparam classBackgroundColor MODEL_COLOR Package Model <>{ Class "<>\nReadOnlyAddressBook" as ReadOnlyAddressBook +Class "<>\nModel" as model Class "<>\nReadOnlyUserPrefs" as ReadOnlyUserPrefs -Class "<>\nModel" as Model Class AddressBook Class ModelManager Class UserPrefs -Class UniquePersonList -Class Person +Class UniqueStudentList +Class Student Class Address Class Email Class Name Class Phone Class Tag +Class Homework +Class Exam +Class Lesson -} +Class UniqueHomeworkList +Class UniqueLessonsList +Class UniqueExamList + +} Class HiddenOutside #FFFFFF -HiddenOutside ..> Model +HiddenOutside .down.|> model +ModelManager .up.|> model AddressBook .up.|> ReadOnlyAddressBook - -ModelManager .up.|> Model -Model .right.> ReadOnlyUserPrefs -Model .left.> ReadOnlyAddressBook +model .right.> ReadOnlyUserPrefs +model .left.> ReadOnlyAddressBook ModelManager -left-> "1" AddressBook ModelManager -right-> "1" UserPrefs UserPrefs .up.|> ReadOnlyUserPrefs -AddressBook *--> "1" UniquePersonList -UniquePersonList --> "~* all" Person -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address -Person *--> "*" Tag + + +AddressBook *--> "1" UniqueStudentList +UniqueStudentList --> "~* all" Student +Student *--> "1" Name +Student *--> "1" Phone +Student *--> "1" Email +Student *--> "1" Address +Student *--> "*" Tag + +Student *--> "1" UniqueHomeworkList +UniqueHomeworkList *--> "*" Homework + +Student *--> "1" UniqueLessonsList +UniqueLessonsList *--> "*" Lesson + +Student *--> "1" UniqueExamList +UniqueExamList *--> "*" Exam + Name -[hidden]right-> Phone -Phone -[hidden]right-> Address -Address -[hidden]right-> Email +Phone -[hidden]right-> Email +Email -[hidden]right-> Address + +ModelManager -->"~* filtered" Student + -ModelManager -->"~* filtered" Person @enduml + + diff --git a/docs/diagrams/ProfileClickSequenceDiagram.puml b/docs/diagrams/ProfileClickSequenceDiagram.puml new file mode 100644 index 00000000000..8792f75bcae --- /dev/null +++ b/docs/diagrams/ProfileClickSequenceDiagram.puml @@ -0,0 +1,60 @@ +@startuml +!include style.puml + +box Ui UI_COLOR_T1 +participant ":StudentLisCard" as StudentListCard UI_COLOR +participant ":MainWindow" as MainWindow UI_COLOR +participant ":DetailedInfoSection" as DetailedInfoSection UI_COLOR +participant ":ProfileContent" as ProfileContent UI_COLOR +end box + +[-> StudentListCard : handleViewProfileButtonClick() +activate StudentListCard + +StudentListCard -> MainWindow : setDetailedHeaderBar(firstName) +activate MainWindow + +MainWindow -> DetailedInfoSection : setDetailedHeaderBar(firstName) +activate DetailedInfoSection + +DetailedInfoSection -> DetailedInfoSection : setHearderBar(firstName) +activate DetailedInfoSection + +DetailedInfoSection --> DetailedInfoSection +deactivate DetailedInfoSection + +DetailedInfoSection --> MainWindow +deactivate DetailedInfoSection + +MainWindow --> StudentListCard +deactivate MainWindow + +create ProfileContent +StudentListCard -> ProfileContent : new ProfileContent(student) +activate ProfileContent + +ProfileContent -> StudentListCard +deactivate ProfileContent + +StudentListCard -> MainWindow : setDetailedContent(ProfileContent) +activate MainWindow + +MainWindow -> DetailedInfoSection : setDetailedContent(ProfileContent) +activate DetailedInfoSection + +DetailedInfoSection -> DetailedInfoSection : setContent(ProfileContent) +activate DetailedInfoSection + +DetailedInfoSection --> DetailedInfoSection +deactivate DetailedInfoSection + +DetailedInfoSection --> MainWindow +deactivate DetailedInfoSection + +MainWindow --> StudentListCard +deactivate MainWindow + +[<-- StudentListCard +deactivate StudentListCard + +@enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index 760305e0e58..daefcedd5eb 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -18,7 +18,7 @@ package "AddressBook Storage" #F4F6F6{ Class "<>\nAddressBookStorage" as AddressBookStorage Class JsonAddressBookStorage Class JsonSerializableAddressBook -Class JsonAdaptedPerson +Class JsonAdaptedStudent Class JsonAdaptedTag } @@ -37,7 +37,7 @@ Storage -right-|> AddressBookStorage JsonUserPrefsStorage .up.|> UserPrefsStorage JsonAddressBookStorage .up.|> AddressBookStorage JsonAddressBookStorage ..> JsonSerializableAddressBook -JsonSerializableAddressBook --> "*" JsonAdaptedPerson -JsonAdaptedPerson --> "*" JsonAdaptedTag +JsonSerializableAddressBook --> "*" JsonAdaptedStudent +JsonAdaptedStudent --> "*" JsonAdaptedTag @enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 95473d5aa19..ba72bd0c7bb 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -11,10 +11,11 @@ Class UiManager Class MainWindow Class HelpWindow Class ResultDisplay -Class PersonListPanel -Class PersonCard +Class StudentListPanel +Class StudentCard Class StatusBarFooter Class CommandBox +Class DetailedInfoSection } package Model <> { @@ -32,26 +33,30 @@ UiManager .left.|> Ui UiManager -down-> "1" MainWindow MainWindow *-down-> "1" CommandBox MainWindow *-down-> "1" ResultDisplay -MainWindow *-down-> "1" PersonListPanel +MainWindow *-down-> "1" StudentListPanel MainWindow *-down-> "1" StatusBarFooter +MainWindow *-down-> "1" DetailedInfoSection MainWindow --> "0..1" HelpWindow -PersonListPanel -down-> "*" PersonCard +StudentListPanel -down-> "*" StudentCard MainWindow -left-|> UiPart ResultDisplay --|> UiPart CommandBox --|> UiPart -PersonListPanel --|> UiPart -PersonCard --|> UiPart +StudentListPanel --|> UiPart +StudentCard --|> UiPart StatusBarFooter --|> UiPart +DetailedInfoSection --|> UiPart HelpWindow --|> UiPart -PersonCard ..> Model +StudentCard ..> Model +DetailedInfoSection ..> Model +DetailedInfoSection <.. StudentCard UiManager -right-> Logic MainWindow -left-> Logic -PersonListPanel -[hidden]left- HelpWindow +StudentListPanel -[hidden]left- HelpWindow HelpWindow -[hidden]left- CommandBox CommandBox -[hidden]left- ResultDisplay ResultDisplay -[hidden]left- StatusBarFooter diff --git a/docs/diagrams/UiDetailedInfoSectionDiagram.puml b/docs/diagrams/UiDetailedInfoSectionDiagram.puml new file mode 100644 index 00000000000..b5080ce69ba --- /dev/null +++ b/docs/diagrams/UiDetailedInfoSectionDiagram.puml @@ -0,0 +1,55 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + +package UI <>{ + +Class "{abstract}\nUiPart" as UiPart + +Class DetailedInfoSection +Class DetailedContent +Class HeaderBar + +Class WelcomeContent +Class ProfileContent +Class "{abstract}\nGeneralHomework" as GeneralHomework +Class "{abstract}\nGeneralLessons" as GeneralLessons +Class "{abstract}\nGeneralExams" as GeneralExams + +Class EmptyHomework +Class EmptyLessons +Class EmptyExams +Class FilledHomework +Class FilledLessons +Class FilledExams +} + +package Model <> { +Class HiddenModel #FFFFFF +} + +Class HiddenOutside #FFFFFF + +DetailedInfoSection *-down-> "1" HeaderBar +DetailedInfoSection *-down-> "1" DetailedContent + +DetailedInfoSection --|> UiPart +HeaderBar --|> UiPart +DetailedContent --|> UiPart + +DetailedInfoSection ..> Model +WelcomeContent -down-|> DetailedContent +ProfileContent -down-|> DetailedContent +GeneralHomework -up-|> DetailedContent +GeneralLessons -up-|> DetailedContent +GeneralExams -up-|> DetailedContent +EmptyExams -up-|> GeneralExams +EmptyHomework -down-|> GeneralHomework +EmptyLessons -up-|> GeneralLessons +FilledExams -up-|> GeneralExams +FilledHomework -down-|> GeneralHomework +FilledLessons -up-|> GeneralLessons + +@enduml diff --git a/docs/diagrams/UiFilledExamsContent.puml b/docs/diagrams/UiFilledExamsContent.puml new file mode 100644 index 00000000000..74f7a94831c --- /dev/null +++ b/docs/diagrams/UiFilledExamsContent.puml @@ -0,0 +1,61 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + +package UI <>{ +Class "<>\nUi" as Ui +Class "{abstract}\nUiPart" as UiPart +Class UiManager +Class MainWindow + +Class DetailedInfoSection +Class DetailedContent +Class HeaderBar + +Class GeneralExamsContent +Class FilledExamsContent +Class PastExamsListPanel +Class UpcomingExamsListPanel +Class ExamCard +} + +package Model <> { +Class HiddenModel #FFFFFF +} + +package Logic <> { +Class HiddenLogic #FFFFFF +} + +Class HiddenOutside #FFFFFF +HiddenOutside ..> Ui + + +UiManager .left.|> Ui +UiManager -down-> "1" MainWindow +MainWindow *-down-> "1" DetailedInfoSection +DetailedInfoSection *-down-> "1" HeaderBar +DetailedInfoSection *-down-> "1" DetailedContent +FilledExamsContent *-down-> "1" PastExamsListPanel +FilledExamsContent *-down-> "1" UpcomingExamsListPanel +PastExamsListPanel -down-> "*" ExamCard +UpcomingExamsListPanel -down-> "*" ExamCard + +MainWindow -left-|> UiPart +DetailedInfoSection --|> UiPart +HeaderBar --|> UiPart +DetailedContent --|> UiPart +PastExamsListPanel --|> UiPart +UpcomingExamsListPanel --|> UiPart +ExamCard --|> UiPart + +DetailedInfoSection ..> Model +UiManager -right-> Logic +MainWindow -left-> Logic +GeneralExamsContent -up-|> DetailedContent +FilledExamsContent -up-|> GeneralExamsContent + +MainWindow -[hidden]-|> UiPart +@enduml diff --git a/docs/diagrams/UiFilledHomeworkContent.puml b/docs/diagrams/UiFilledHomeworkContent.puml new file mode 100644 index 00000000000..13ffe4f4227 --- /dev/null +++ b/docs/diagrams/UiFilledHomeworkContent.puml @@ -0,0 +1,60 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + +package UI <>{ +Class "<>\nUi" as Ui +Class "{abstract}\nUiPart" as UiPart +Class UiManager +Class MainWindow + +Class DetailedInfoSection +Class DetailedContent +Class HeaderBar + +Class GeneralHomeworkContent +Class FilledHomeworkContent +Class HomeworkListPanel +Class HomeworkCard +Class HomeworkPieChart +} + +package Model <> { +Class HiddenModel #FFFFFF +} + +package Logic <> { +Class HiddenLogic #FFFFFF +} + +Class HiddenOutside #FFFFFF +HiddenOutside ..> Ui + + +UiManager .left.|> Ui +UiManager -down-> "1" MainWindow +MainWindow *-down-> "1" DetailedInfoSection +DetailedInfoSection *-down-> "1" HeaderBar +DetailedInfoSection *-down-> "1" DetailedContent +FilledHomeworkContent *-down-> "1" HomeworkListPanel +FilledHomeworkContent *-down-> "1" HomeworkPieChart +HomeworkListPanel -down-> "*" HomeworkCard + +MainWindow -left-|> UiPart +DetailedInfoSection --|> UiPart +HeaderBar --|> UiPart +DetailedContent --|> UiPart +HomeworkListPanel --|> UiPart +HomeworkCard --|> UiPart +HomeworkPieChart --|> UiPart + +DetailedInfoSection ..> Model +UiManager -right-> Logic +MainWindow -left-> Logic +GeneralHomeworkContent -up-|> DetailedContent +FilledHomeworkContent -up-|> GeneralHomeworkContent + +MainWindow -[hidden]-|> UiPart +@enduml diff --git a/docs/diagrams/UiFilledLessonsContent.puml b/docs/diagrams/UiFilledLessonsContent.puml new file mode 100644 index 00000000000..97cf8f30831 --- /dev/null +++ b/docs/diagrams/UiFilledLessonsContent.puml @@ -0,0 +1,61 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + +package UI <>{ +Class "<>\nUi" as Ui +Class "{abstract}\nUiPart" as UiPart +Class UiManager +Class MainWindow + +Class DetailedInfoSection +Class DetailedContent +Class HeaderBar + +Class GeneralLessonsContent +Class FilledLessonsContent +Class PastLessonsListPanel +Class UpcomingLessonsListPanel +Class LessonsCard +} + +package Model <> { +Class HiddenModel #FFFFFF +} + +package Logic <> { +Class HiddenLogic #FFFFFF +} + +Class HiddenOutside #FFFFFF +HiddenOutside ..> Ui + + +UiManager .left.|> Ui +UiManager -down-> "1" MainWindow +MainWindow *-down-> "1" DetailedInfoSection +DetailedInfoSection *-down-> "1" HeaderBar +DetailedInfoSection *-down-> "1" DetailedContent +FilledLessonsContent *-down-> "1" PastLessonsListPanel +FilledLessonsContent *-down-> "1" UpcomingLessonsListPanel +PastLessonsListPanel -down-> "*" LessonsCard +UpcomingLessonsListPanel -down-> "*" LessonsCard + +MainWindow -left-|> UiPart +DetailedInfoSection --|> UiPart +HeaderBar --|> UiPart +DetailedContent --|> UiPart +PastLessonsListPanel --|> UiPart +UpcomingLessonsListPanel --|> UiPart +LessonsCard --|> UiPart + +DetailedInfoSection ..> Model +UiManager -right-> Logic +MainWindow -left-> Logic +GeneralLessonsContent -up-|> DetailedContent +FilledLessonsContent -up-|> GeneralLessonsContent + +MainWindow -[hidden]-|> UiPart +@enduml diff --git a/docs/diagrams/UiProfileContent.puml b/docs/diagrams/UiProfileContent.puml new file mode 100644 index 00000000000..1bb7fa30365 --- /dev/null +++ b/docs/diagrams/UiProfileContent.puml @@ -0,0 +1,56 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + +package UI <>{ +Class "<>\nUi" as Ui +Class "{abstract}\nUiPart" as UiPart +Class UiManager +Class MainWindow + +Class DetailedInfoSection +Class DetailedContent +Class HeaderBar + +Class ProfileContent +} + +package Model <> { +Class Name MODEL_COLOR +Class Email MODEL_COLOR +Class Phone MODEL_COLOR +Class Address MODEL_COLOR +} + +package Logic <> { +Class HiddenLogic #FFFFFF +} + +Class HiddenOutside #FFFFFF +HiddenOutside ..> Ui + + +UiManager .left.|> Ui +UiManager -down-> "1" MainWindow +MainWindow *-down-> "1" DetailedInfoSection +DetailedInfoSection *-down-> "1" HeaderBar +DetailedInfoSection *-down-> "1" DetailedContent +ProfileContent *-down-> "1" Name +ProfileContent *-down-> "1" Email +ProfileContent *-down-> "1" Phone +ProfileContent *-down-> "1" Address + +MainWindow -left-|> UiPart +DetailedInfoSection --|> UiPart +HeaderBar --|> UiPart +DetailedContent --|> UiPart + +DetailedInfoSection ..> Model +UiManager -right-> Logic +MainWindow -left-> Logic +ProfileContent -up-|> DetailedContent + +MainWindow -[hidden]-|> UiPart +@enduml diff --git a/docs/diagrams/UiQuickAccessButton.png b/docs/diagrams/UiQuickAccessButton.png new file mode 100644 index 00000000000..bd07edc8e52 Binary files /dev/null and b/docs/diagrams/UiQuickAccessButton.png differ diff --git a/docs/diagrams/UiQuickAccessButton.puml b/docs/diagrams/UiQuickAccessButton.puml new file mode 100644 index 00000000000..72aa90f4f23 --- /dev/null +++ b/docs/diagrams/UiQuickAccessButton.puml @@ -0,0 +1,35 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor UI_COLOR_T4 +skinparam classBackgroundColor UI_COLOR + +package UI <>{ +Class "{abstract}\nUiPart" as UiPart +Class StudentListPanel +Class StudentCard +Class ProfileButton +Class HomeworkButton +Class LessonsButton +Class ExamsButton +} + +package Model <> { +Class HiddenModel #FFFFFF +} + +StudentCard *-down-> "1" ProfileButton +StudentCard *-down-> "1" HomeworkButton +StudentCard *-down-> "1" LessonsButton +StudentCard *-down-> "1" ExamsButton + +StudentListPanel -down-> "*" StudentCard +StudentListPanel --|> UiPart +StudentCard --|> UiPart +ProfileButton --|> UiPart +HomeworkButton --|> UiPart +LessonsButton --|> UiPart +ExamsButton --|> UiPart + +StudentCard ..> Model +@enduml diff --git a/docs/diagrams/tracing/LogicSequenceDiagram.puml b/docs/diagrams/tracing/LogicSequenceDiagram.puml index fdcbe1c0ccc..56fc24eadcd 100644 --- a/docs/diagrams/tracing/LogicSequenceDiagram.puml +++ b/docs/diagrams/tracing/LogicSequenceDiagram.puml @@ -13,7 +13,7 @@ create ecp abp -> ecp abp -> ecp ++: parse(arguments) create ec -ecp -> ec ++: index, editPersonDescriptor +ecp -> ec ++: index, editStudentDescriptor ec --> ecp -- ecp --> abp --: command abp --> logic --: command diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png index 2f1346869d0..22fbc2ae28a 100644 Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/ClickButtonActivityDiagram.png b/docs/images/ClickButtonActivityDiagram.png new file mode 100644 index 00000000000..8516b62c943 Binary files /dev/null and b/docs/images/ClickButtonActivityDiagram.png differ diff --git a/docs/images/CreateLessonAD.png b/docs/images/CreateLessonAD.png new file mode 100644 index 00000000000..927d1a8326d Binary files /dev/null and b/docs/images/CreateLessonAD.png differ diff --git a/docs/images/CreateLessonSequenceDiagram.png b/docs/images/CreateLessonSequenceDiagram.png new file mode 100644 index 00000000000..ce45d15b2dd Binary files /dev/null and b/docs/images/CreateLessonSequenceDiagram.png differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index fa327b39618..85bb79db9b5 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/Exams.jpg b/docs/images/Exams.jpg new file mode 100644 index 00000000000..4a8ccdd70f2 Binary files /dev/null and b/docs/images/Exams.jpg differ diff --git a/docs/images/ExamsClickSequenceDiagram.png b/docs/images/ExamsClickSequenceDiagram.png new file mode 100644 index 00000000000..40868f235e8 Binary files /dev/null and b/docs/images/ExamsClickSequenceDiagram.png differ diff --git a/docs/images/GUI.jpg b/docs/images/GUI.jpg new file mode 100644 index 00000000000..91f17cd5890 Binary files /dev/null and b/docs/images/GUI.jpg differ diff --git a/docs/images/Homework.png b/docs/images/Homework.png new file mode 100644 index 00000000000..6464d69470a Binary files /dev/null and b/docs/images/Homework.png differ diff --git a/docs/images/HomeworkClickSequenceDiagram.png b/docs/images/HomeworkClickSequenceDiagram.png new file mode 100644 index 00000000000..d59694300cb Binary files /dev/null and b/docs/images/HomeworkClickSequenceDiagram.png differ diff --git a/docs/images/Lessons.jpg b/docs/images/Lessons.jpg new file mode 100644 index 00000000000..4c400824878 Binary files /dev/null and b/docs/images/Lessons.jpg differ diff --git a/docs/images/LessonsClickSequenceDiagram.png b/docs/images/LessonsClickSequenceDiagram.png new file mode 100644 index 00000000000..90a2af539ca Binary files /dev/null and b/docs/images/LessonsClickSequenceDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 04070af60d8..c0eb994fbaf 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/Profile.jpg b/docs/images/Profile.jpg new file mode 100644 index 00000000000..2d6ba191561 Binary files /dev/null and b/docs/images/Profile.jpg differ diff --git a/docs/images/ProfileClickSequenceDiagram.png b/docs/images/ProfileClickSequenceDiagram.png new file mode 100644 index 00000000000..a8dfcb85114 Binary files /dev/null and b/docs/images/ProfileClickSequenceDiagram.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 2533a5c1af0..a965cff6c11 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/Thumbnail.png b/docs/images/Thumbnail.png new file mode 100644 index 00000000000..1e54156c1d8 Binary files /dev/null and b/docs/images/Thumbnail.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..be7a87169cc 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png index 785e04dbab4..56f15f3bba2 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UiDetailedInfoSectionDiagram.png b/docs/images/UiDetailedInfoSectionDiagram.png new file mode 100644 index 00000000000..af448da26b3 Binary files /dev/null and b/docs/images/UiDetailedInfoSectionDiagram.png differ diff --git a/docs/images/UiFilledExamsContent.png b/docs/images/UiFilledExamsContent.png new file mode 100644 index 00000000000..37175fadff2 Binary files /dev/null and b/docs/images/UiFilledExamsContent.png differ diff --git a/docs/images/UiFilledHomeworkContent.png b/docs/images/UiFilledHomeworkContent.png new file mode 100644 index 00000000000..9f3ee02e3c2 Binary files /dev/null and b/docs/images/UiFilledHomeworkContent.png differ diff --git a/docs/images/UiFilledLessonsContent.png b/docs/images/UiFilledLessonsContent.png new file mode 100644 index 00000000000..5b724717cec Binary files /dev/null and b/docs/images/UiFilledLessonsContent.png differ diff --git a/docs/images/UiProfileContent.png b/docs/images/UiProfileContent.png new file mode 100644 index 00000000000..3faf37612f8 Binary files /dev/null and b/docs/images/UiProfileContent.png differ diff --git a/docs/images/UiQuickAccessButton.png b/docs/images/UiQuickAccessButton.png new file mode 100644 index 00000000000..633a7401e71 Binary files /dev/null and b/docs/images/UiQuickAccessButton.png differ diff --git a/docs/images/delete-homework.jpg b/docs/images/delete-homework.jpg new file mode 100644 index 00000000000..aacc0385748 Binary files /dev/null and b/docs/images/delete-homework.jpg differ diff --git a/docs/images/delete-lesson.jpg b/docs/images/delete-lesson.jpg new file mode 100644 index 00000000000..fccba34a462 Binary files /dev/null and b/docs/images/delete-lesson.jpg differ diff --git a/docs/images/delete.png b/docs/images/delete.png new file mode 100644 index 00000000000..f9471a951a9 Binary files /dev/null and b/docs/images/delete.png differ diff --git a/docs/images/entireList1.jpg b/docs/images/entireList1.jpg new file mode 100644 index 00000000000..9fc7ff60121 Binary files /dev/null and b/docs/images/entireList1.jpg differ diff --git a/docs/images/entireList2.jpg b/docs/images/entireList2.jpg new file mode 100644 index 00000000000..87cdaf21aa2 Binary files /dev/null and b/docs/images/entireList2.jpg differ diff --git a/docs/images/entireList3.jpg b/docs/images/entireList3.jpg new file mode 100644 index 00000000000..cdfc5f51119 Binary files /dev/null and b/docs/images/entireList3.jpg differ diff --git a/docs/images/fahim-tazz.png b/docs/images/fahim-tazz.png new file mode 100644 index 00000000000..ba30d7f71b1 Binary files /dev/null and b/docs/images/fahim-tazz.png differ diff --git a/docs/images/mark-homework.jpg b/docs/images/mark-homework.jpg new file mode 100644 index 00000000000..99709b2e10f Binary files /dev/null and b/docs/images/mark-homework.jpg differ diff --git a/docs/images/nbqian.png b/docs/images/nbqian.png new file mode 100644 index 00000000000..a5a7a0895f9 Binary files /dev/null and b/docs/images/nbqian.png differ diff --git a/docs/images/new-homework.jpg b/docs/images/new-homework.jpg new file mode 100644 index 00000000000..2ea84e6c040 Binary files /dev/null and b/docs/images/new-homework.jpg differ diff --git a/docs/images/new-lesson.jpg b/docs/images/new-lesson.jpg new file mode 100644 index 00000000000..b35e8303ff2 Binary files /dev/null and b/docs/images/new-lesson.jpg differ diff --git a/docs/images/new-student.png b/docs/images/new-student.png new file mode 100644 index 00000000000..dc5c65b1730 Binary files /dev/null and b/docs/images/new-student.png differ diff --git a/docs/images/szejiancheng.png b/docs/images/szejiancheng.png new file mode 100644 index 00000000000..31b23c11fce Binary files /dev/null and b/docs/images/szejiancheng.png differ diff --git a/docs/images/unmark-homework.jpg b/docs/images/unmark-homework.jpg new file mode 100644 index 00000000000..a06a540a49a Binary files /dev/null and b/docs/images/unmark-homework.jpg differ diff --git a/docs/images/update-homework.jpg b/docs/images/update-homework.jpg new file mode 100644 index 00000000000..1e6e49ceb20 Binary files /dev/null and b/docs/images/update-homework.jpg differ diff --git a/docs/images/update-info.png b/docs/images/update-info.png new file mode 100644 index 00000000000..02365a8b9b0 Binary files /dev/null and b/docs/images/update-info.png differ diff --git a/docs/images/update-lesson.jpg b/docs/images/update-lesson.jpg new file mode 100644 index 00000000000..0a49a8070bf Binary files /dev/null and b/docs/images/update-lesson.jpg differ diff --git a/docs/images/view-exam.png b/docs/images/view-exam.png new file mode 100644 index 00000000000..439698d1372 Binary files /dev/null and b/docs/images/view-exam.png differ diff --git a/docs/images/view-homework.jpg b/docs/images/view-homework.jpg new file mode 100644 index 00000000000..23f6be4e449 Binary files /dev/null and b/docs/images/view-homework.jpg differ diff --git a/docs/images/view-lesson.jpg b/docs/images/view-lesson.jpg new file mode 100644 index 00000000000..a3dc6c14b65 Binary files /dev/null and b/docs/images/view-lesson.jpg differ diff --git a/docs/images/view-profile.png b/docs/images/view-profile.png new file mode 100644 index 00000000000..4934b990163 Binary files /dev/null and b/docs/images/view-profile.png differ diff --git a/docs/images/yufannnn.png b/docs/images/yufannnn.png new file mode 100644 index 00000000000..ef9567c17dd Binary files /dev/null and b/docs/images/yufannnn.png differ diff --git a/docs/img.png b/docs/img.png new file mode 100644 index 00000000000..439698d1372 Binary files /dev/null and b/docs/img.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..fdf07d1d1e1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,18 +1,18 @@ --- layout: page -title: AddressBook Level-3 +title: TutorPro --- -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) -[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3) +[![Java CI](https://github.com/AY2223S2-CS2103T-W13-4/tp/actions/workflows/gradle.yml/badge.svg)](https://github.com/AY2223S2-CS2103T-W13-4/tp/actions/workflows/gradle.yml) ![Ui](images/Ui.png) -**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). - -* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). -* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. +TutorPro +* TutorPro is a desktop application for managing contacts of students and classes, optimised for use via a Command Line Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). +* For the detailed documentation of this project, see the **[TutorPro Website](https://ay2223s2-cs2103t-w13-4.github.io/tp/)**. +* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org#https://se-education.org/#contributing) for more info. +* This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org). **Acknowledgements** diff --git a/docs/team/fahim-tazz.md b/docs/team/fahim-tazz.md new file mode 100644 index 00000000000..5d8b15a1a0f --- /dev/null +++ b/docs/team/fahim-tazz.md @@ -0,0 +1,47 @@ +--- +layout: page +title: Muhammad Fahim Tajwar's Project Portfolio Page +--- + +### Project: TutorPro + +TutorPro is a desktop app designed to help private tutors manage their student information effectively. +With TutorPro, tutors can easily keep track of their students' details, lesson plans, homeworks, and exams, all in one place. +TutorPro uses a combination of GUI and CLI to provide an aesthetically-pleasing UI for viewing student's information, while promising the +speed and control of a CLI. Whether you're managing a handful of students or hundreds, + +TutorPro can help you streamline your workflow and make your tutoring experience much more efficient. + +Given below are my contributions to the project. + + +* **New Feature**: Track School name and Grade level in Student's profile. + - What it does: Allows user to add an optional School name and Grade level. + - Justification: This feature enables tutors to tag students with their School name and Grade Level, for easy reference on the student list. + - Highlights: This feature can allow future enhancements for batch processing, such as adding a homework to all students of a particular grade level. + +* **Code contributed**: [Reposense Link](https://nus-cs2103-ay2223s2.github.io/tp-dashboard/?search=fahim-tazz&breakdown=true) + + +* **Project management**: + * Managed release v1.3 + * Renamed instances of AB-3 to TutorPro in Github pages. + * Defined Use Cases + * Designed a Product pitch, with a formal Business Plan (for CS2101). + * Create PR to the upstream Repo + +* **Enhancements to new Features**: + * Added unit testing for most of the newly created Parser and Command classes. + +* **Contributions to the UG**: + * Created new User Guide for v1.2. + * Finalized and Polished User Guide for v1.3 and v1.4. + +* **Contributions to the DG**: + * User Stories + * Non-Functional Requirements + * Use cases + +* **Community**: + * PRs reviewed (with non-trivial review comments): 10 + * PRs authored : 26 diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md deleted file mode 100644 index 773a07794e2..00000000000 --- a/docs/team/johndoe.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -layout: page -title: John Doe's Project Portfolio Page ---- - -### Project: AddressBook Level 3 - -AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. - -Given below are my contributions to the project. - -* **New Feature**: Added the ability to undo/redo previous commands. - * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. - * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. - * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. - * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}* - -* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys. - -* **Code contributed**: [RepoSense link]() - -* **Project management**: - * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub - -* **Enhancements to existing features**: - * Updated the GUI color scheme (Pull requests [\#33](), [\#34]()) - * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]()) - -* **Documentation**: - * User Guide: - * Added documentation for the features `delete` and `find` [\#72]() - * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]() - * Developer Guide: - * Added implementation details of the `delete` feature. - -* **Community**: - * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]() - * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]()) - * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]()) - * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]()) - -* **Tools**: - * Integrated a third party library (Natty) to the project ([\#42]()) - * Integrated a new Github plugin (CircleCI) to the team repo - -* _{you can add/remove categories in the list above}_ diff --git a/docs/team/nbqian.md b/docs/team/nbqian.md new file mode 100644 index 00000000000..0d6cf343799 --- /dev/null +++ b/docs/team/nbqian.md @@ -0,0 +1,60 @@ +--- +layout: page +title: Niu Boqian's Project Portfolio Page +--- + +### Project: TutorPro + +TutorPro is a desktop app designed to help private tutors manage their student information effectively. With TutorPro, tutors can easily keep track of their students' addresses, contact details, lessons, homework, and progress, all in one place. Its ability to keep track of a student's lessons and exams makes it especially useful for tutors to plan their lessons to prepare his/her students for their exams. + +Given below are my contributions to the project. + +* **New Feature**: Lesson Feature and Lesson Related commands (new-lesson, view-lesson, delete-lesson, update-lesson) + * What it does: Allows the user to add, view, delete, and update lessons for a student. + * Justification: This feature improves the product significantly because a tutor can manage the lessons of a student more efficiently. + * Highlights: + * The commands are simple to use. The view-lesson command is especially flexible as the user can filter by date, name of student, subject, or any combination of these. + * I created a storage for the lesson list, so that now each json file of a student will have a lesson list. + * I created relevant helper classes to help with the implementation of the lesson feature. This include various predicate and exception classes. + * I create test classes for various classes in the lesson feature. + * Credits: N/A + +* **New Feature**: Exam Related Commands (view-exam, delete-exam, update-exam) + * What it does: Allows the user to view, delete, and update exams for a student. + * Justification: This feature improves the product significantly because a tutor can be aware of the exams of a student and can plan the lessons accordingly. + * Highlights: + * The commands are simple to use. The view-exam command is especially flexible as the user can filter by date, name of student, exam name, or any combination of these. + * I created a storage for the exam list, so that now each json file of a student will have a exam list. + * I created relevant helper classes to help with the implementation of the exam feature. This include various predicate and exception classes. + * Credits: N/A + + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s2.github.io/tp-dashboard/?search=nbqian&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2023-02-17&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + +* **Project management**: + * Created and assigned issues to team members on GitHub and kept track of their progress + * Created labels and categorized issues on GitHub +* **Enhancements to existing features**: + * Changed the find, edit, delete command such that they now require the user to use prefixes to specify the fields to search for, edit, or delete + * Changed the add and edit command such that when a new student is created or an existing student is modified, his/her name cannot be part of any existing students' names, and vice versa. + * Added lesson unique lists in the application, added, and updated relevant methods in a logical model, storage, and other classes ot fit the change. +* **Documentation**: + * User Guide: + * Added documentation for the features `new-lesson`, `view-lesson`, `delete-lesson`, `update-lesson`, `new-exam`, `view-exam`, `delete-exam`, `update-exam`: [#184](https://github.com/AY2223S2-CS2103T-W13-4/tp/pull/184) + * Added Glossary, List of Commands, and List of Prefixes as 3 separate tables: [#117](https://github.com/AY2223S2-CS2103T-W13-4/tp/pull/117) + * Developer Guide: + * Added implementation for `CreateLessonCommand` with Sequence Diagram and Activity Diagram [#89](https://github.com/AY2223S2-CS2103T-W13-4/tp/pull/89) + * Added Glossary and changed the table of contents [#198](https://github.com/AY2223S2-CS2103T-W13-4/tp/pull/198) + +* **Community**: + * Reported bugs and suggestions for other teams in the class: + [#1](https://github.com/NBQian/ped/issues/1) + [#2](https://github.com/NBQian/ped/issues/2) + [#3](https://github.com/NBQian/ped/issues/3) + [#4](https://github.com/NBQian/ped/issues/4) + [#5](https://github.com/NBQian/ped/issues/5) + [#6](https://github.com/NBQian/ped/issues/6) + [#7](https://github.com/NBQian/ped/issues/7) + [#8](https://github.com/NBQian/ped/issues/8) +* **Tools**: +* Use PlantUML to add more UML diagrams in the developer guide. diff --git a/docs/team/szejiancheng.md b/docs/team/szejiancheng.md new file mode 100644 index 00000000000..c51a579f8d0 --- /dev/null +++ b/docs/team/szejiancheng.md @@ -0,0 +1,42 @@ +--- +layout: page +title: Sze Jian Cheng's Project Portfolio Page +--- + +### Project: TutorPro + +TutorPro is a desktop app designed to help private tutors manage their student information effectively. With TutorPro, tutors can easily keep track of their students' addresses, contact details, lessons, homework, and progress, all in one place. This app is optimised for use via a Graphical User Interface (GUI), allowing tutors to interact with the app using easy-to-understand buttons and menus. However, TutorPro also provides a Command Line Interface (CLI) for those who prefer a faster way of getting things done. Whether you're managing a handful of students or hundreds, TutorPro can help you streamline your workflow and make your tutoring experience more efficient. + +Given below are my contributions to the project. + +* **New Feature**: Adds the ability to track exams for a particular student. + - What it does: Allows user to add, manipulate, and delete exams of a particular student, including details such as + the name of the exam, its start time, its end time, weightage, and grade. + - Justification: This feature enables tutors to effectively track of upcoming and completed exams. + - Highlights: This feature required additional classes for parsing and data storage that needed to be + implemented in order to function with the existing project structure. + (see [exam parsers](https://github.com/AY2223S2-CS2103T-W13-4/tp/tree/master/src/main/java/seedu/address/logic/parser/exam) and + [exam commands](https://github.com/AY2223S2-CS2103T-W13-4/tp/tree/master/src/main/java/seedu/address/logic/commands/exam).) + - Credits: @nbqian for providing the basic skeleton for similar student attributes (see lesson-related classes) + + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s2.github.io/tp-dashboard/?search=szejiancheng&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2023-02-17&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + +* **Project management**: + * Managed releases `v1.2`-`v1.4` on Github + * Bug reporting for all versions (4 releases) + +* **Documentation**: + * User Guide: + * Authored exam-related commands in user guide + * Developer Guide: + * Brainstormed user stories and use-cases during conceptualization phase. + * UML diagrams: + * contributed to tweaking of existing UML diagrams using PlantUML + +* **Community**: + * PRs reviewed : 7 + * PRs authored : 23 + +* **Others**: + * Created professional demo video with voiceover for product presentation using Davinci Resolve diff --git a/docs/team/yufannnn.md b/docs/team/yufannnn.md new file mode 100644 index 00000000000..82cb29dd3d1 --- /dev/null +++ b/docs/team/yufannnn.md @@ -0,0 +1,74 @@ +--- +layout: page +title: Zhu Yufan's Project Portfolio Page +--- + +### Project: TutorPro + +TutorPro is a desktop app designed to help private tutors manage their student information effectively. +Offers both a Command Line Interface (CLI) and a Graphical User Interface (GUI) for managing student information. +The CLI provides a fast and efficient way to keep track of students' details, homework, lessons, and exams, +while the GUI offers an intuitive visual interface. +Whether tutoring a few or many students, TutorPro can streamline workflow. + +Given below are my contributions to the project. + +* **Idea**: Came up with the idea of enhancing AB3 to a student management application for private tutors. + +* **New Feature**: Homework Feature and Six New Homework Related commands + - what it does: The Homework Feature and Homework related commands are designed to help tutors manage their student homework tasks more efficiently. It allows tutors to create, view, update, and delete homework tasks using a set of commands. The new commands include: new-homework, view-homework, delete-homework, make-homework, unmark-homework, and update-homework commands. + - Justification: The Homework Feature is aimed at addressing the common problem of managing multiple homework tasks for private tutors. With this feature, users can easily keep track of their students' assignments and deadlines, prioritize their work, and avoid missing important submission dates. + - Highlights: The Homework Feature is easy to use and can be accessed through a set of simple commands. The feature helps tutors stay organized and on top of the students' homework tasks, reducing the risk of missing important deadlines. + - Credit: N/A + +* **New Feature**: Redesigned GUI and Detailed Information Section + - what it does: The new feature redesigns the UI of the application and adds a Detailed Information Section that provides more specific information about each student. The Detailed Information Section includes a Profile Page, a Homework Page, a Lesson Page, and an Exam Page. Each page is accessible through quick access buttons on the student card. + - Justification: The redesigned UI and Detailed Information Section are aimed at improving the user experience of the application, making the application more aesthetically appealing. It provides a more user-friendly interface for tutors to manage their students' information and allows for more efficient navigation between different types of information. + - Highlights: The Detailed Information Section provides more specific information about each student, including their personal information, homework assignments, past and upcoming lessons, and past and upcoming exams. + - Credit: N/A + +* **New Feature**: New Feature: Quick Access Button on Each Student Card + - What it does: The Quick Access Button is a new feature that provides a more user-friendly interface for tutors to access student-specific information. It adds four buttons on each student card, including profile, homework, lessons, and exams. By clicking on any of these buttons, the user can quickly access the corresponding information for the selected student. + - Justification: The Quick Access Button feature is aimed at improving the user experience for tutors who need to manage multiple students' profiles, homework, lessons, and exams. It helps to streamline the process of accessing student-specific information and eliminates the need for users to search through long lists to find the information they need. + - Highlights: The Quick Access Button is easy to use and provides users with quick access to the information they need. The feature is intuitive and visually appealing, making it easy for users to navigate through the app and access the relevant information. + - Credit: N/A + +* **Code contributed**: [RepoSense Link](https://nus-cs2103-ay2223s2.github.io/tp-dashboard/?search=Yufannnn&sort=groupTitle%20dsc&sortWithin=title&since=2023-02-17&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=zoom&zA=Yufannnn&zR=AY2223S2-CS2103T-W13-4%2Ftp%5Bmaster%5D&zACS=247.67299412915852&zS=2023-02-17&zFS=&zU=2023-04-05&zMG=false&zFTF=commit&zFGS=groupByRepos&zFR=false) + +* **Project management**: + * Set up Team Repo + * Updated Workflow: [#1](https://github.com/AY2223S2-CS2103T-W13-4/tp/pull/1) + * Created and assigned issues to team members on GitHub and kept track of their progress + * Created labels and categorized issues on GitHub + * Created and manages milestones on GitHub, changed their due dates, and closed them to wrap-up. + * Managed releases `v1.3.Trail` on GitHub ([Link to v1.3 trail release](https://github.com/AY2223S2-CS2103T-W13-4/tp/releases/tag/v1.3.trial)) + +* **Enhancements to existing features**: + * Refactor the Person model to a Student model and added relevant methods in a logic model, storage and other class to fit the change: [#1](https://github.com/AY2223S2-CS2103T-W13-4/tp/pull/1) + * Added homework unique lists in the application, added, and updated relevant methods in logic, model, storage and other class to fit the changes: [#13](https://github.com/AY2223S2-CS2103T-W13-4/tp/pull/13) + * Redesign the GUI to make it more aesthetically appealing and add more sections on the GUI: [#56](https://github.com/AY2223S2-CS2103T-W13-4/tp/pull/13) + +* **Documentation**: + * User Guide: + * Updated the `Quick Start` section and update TOC: [#170](https://github.com/AY2223S2-CS2103T-W13-4/tp/pull/170) + * Added documentation for the new GUI I redesigned: [#124](https://github.com/AY2223S2-CS2103T-W13-4/tp/pull/124) + * Added documentation for the new six homework commands I created: [#170](https://github.com/AY2223S2-CS2103T-W13-4/tp/pull/170) + * Added documentation for the Unique Mechanism Section I created: [#186](https://github.com/AY2223S2-CS2103T-W13-4/tp/pull/186), [#193](https://github.com/AY2223S2-CS2103T-W13-4/tp/pull/193) + * Developer Guide: + * Added implementation for `Detailed Information Section and Quick Access Button`: [#80](https://github.com/AY2223S2-CS2103T-W13-4/tp/pull/80), [#81](https://github.com/AY2223S2-CS2103T-W13-4/tp/pull/81) + * Updated `UI component Section`:[#80](https://github.com/AY2223S2-CS2103T-W13-4/tp/pull/80), [#81](https://github.com/AY2223S2-CS2103T-W13-4/tp/pull/81) +* **Community**: + * PRs reviewed (with non-trivial review comments): + [#44](https://github.com/AY2223S2-CS2103T-W13-4/tp/pull/44), + [#70](https://github.com/AY2223S2-CS2103T-W13-4/tp/pull/70). + * Reported bugs and suggestions for other teams in the class: + [#106](https://github.com/AY2223S2-CS2103T-W10-3/tp/issues/106), + [#116](https://github.com/AY2223S2-CS2103T-W10-3/tp/issues/116), + [#118](https://github.com/AY2223S2-CS2103T-W10-3/tp/issues/118), + [#120](https://github.com/AY2223S2-CS2103T-W10-3/tp/issues/120), + [#123](https://github.com/AY2223S2-CS2103T-W10-3/tp/issues/123), + [#131](https://github.com/AY2223S2-CS2103T-W10-3/tp/issues/131). + * Some manual testing was done to ensure that the UI worked fine. +* **Tools**: + * Use JavaFX and Scene Builder to modify the UI. + * Use PlantUML to add more UML diagrams in the developer guide. diff --git a/docs/tutorials/AddRemark.md b/docs/tutorials/AddRemark.md index 880c701042f..98c0fd08d15 100644 --- a/docs/tutorials/AddRemark.md +++ b/docs/tutorials/AddRemark.md @@ -229,7 +229,7 @@ Now that we have all the information that we need, let’s lay the groundwork fo ### Add a new `Remark` class -Create a new `Remark` in `seedu.address.model.person`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code. +Create a new `Remark` in `seedu.address.model.student`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code. A copy-paste and search-replace later, you should have something like [this](https://github.com/se-edu/addressbook-level3/commit/4516e099699baa9e2d51801bd26f016d812dedcc#diff-41bb13c581e280c686198251ad6cc337cd5e27032772f06ed9bf7f1440995ece). Note how `Remark` has no constrains and thus does not require input validation. @@ -242,7 +242,7 @@ Let’s change `RemarkCommand` and `RemarkCommandParser` to use the new `Remark` Without getting too deep into `fxml`, let’s go on a 5 minute adventure to get some placeholder text to show up for each person. -Simply add the following to [`seedu.address.ui.PersonCard`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-639834f1e05afe2276a86372adf0fe5f69314642c2d93cfa543d614ce5a76688). +Simply add the following to [`seedu.address.ui.student.StudentCard`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-639834f1e05afe2276a86372adf0fe5f69314642c2d93cfa543d614ce5a76688). **`PersonCard.java`:** diff --git a/docs/tutorials/RemovingFields.md b/docs/tutorials/RemovingFields.md index f29169bc924..1f8f05e80f6 100644 --- a/docs/tutorials/RemovingFields.md +++ b/docs/tutorials/RemovingFields.md @@ -28,7 +28,7 @@ IntelliJ IDEA provides a refactoring tool that can identify *most* parts of a re ### Assisted refactoring -The `address` field in `Person` is actually an instance of the `seedu.address.model.person.Address` class. Since removing the `Address` class will break the application, we start by identifying `Address`'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` \> `Safe Delete` through the menu. +The `address` field in `Person` is actually an instance of the `seedu.address.model.student.Address` class. Since removing the `Address` class will break the application, we start by identifying `Address`'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` \> `Safe Delete` through the menu. * :bulb: To make things simpler, you can unselect the options `Search in comments and strings` and `Search for text occurrences` ![Usages detected](../images/remove/UnsafeDelete.png) diff --git a/docs/tutorials/TracingCode.md b/docs/tutorials/TracingCode.md index 4fb62a83ef6..211a206c9a7 100644 --- a/docs/tutorials/TracingCode.md +++ b/docs/tutorials/TracingCode.md @@ -190,7 +190,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ public CommandResult execute(Model model) throws CommandException { ... Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); + Person editedPerson = createEditedPerson(personToEdit, editStudentDescriptor); if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { throw new CommandException(MESSAGE_DUPLICATE_PERSON); } diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 4133aaa0151..4d6bbc493f1 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -48,7 +48,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing TutorPro ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -79,14 +79,14 @@ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { try { addressBookOptional = storage.readAddressBook(); if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + logger.info("Data file not found. Will be starting with a sample TutorPro"); } initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); + logger.warning("Data file not in the correct format. Will be starting with an empty TutorPro"); initialData = new AddressBook(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. Will be starting with an empty TutorPro"); initialData = new AddressBook(); } @@ -151,7 +151,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { + "Using default user prefs"); initializedPrefs = new UserPrefs(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. Will be starting with an empty TutorPro"); initializedPrefs = new UserPrefs(); } @@ -167,13 +167,13 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting TutorPro " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping TutorPro ] ============================="); try { storage.saveUserPrefs(model.getUserPrefs()); } catch (IOException e) { diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e469..917beb41471 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -4,10 +4,94 @@ * Container for user visible messages. */ public class Messages { - public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; - public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; - + public static final String MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX = "The student index provided is invalid"; + public static final String MESSAGE_STUDENTS_LISTED_OVERVIEW = "%d students listed!\n%s"; + public static final String MESSAGE_HOMEWORK_ADDED_SUCCESS = "New homework added:\n%s\n" + + "To the following students:\n%s"; + public static final String MESSAGE_HOMEWORK_LISTED_OVERVIEW = "%d homework from %d student listed:\n%s"; + public static final String MESSAGE_ALL_HOMEWORK_LISTED_OVERVIEW = "%d homework listed:\n%s"; + public static final String MESSAGE_NO_HOMEWORK_FOUND = "No homework found"; + public static final String MESSAGE_INVALID_HOMEWORK_DISPLAYED_INDEX = "The homework index provided is invalid"; + public static final String MESSAGE_HOMEWORK_DELETED_SUCCESS = "Homework : %s. %s\n" + + "Deleted from the student %s\n"; + public static final String MESSAGE_LESSON_ADDED_SUCCESS = "New lesson added: \n%s \n" + + "To the following students: \n%s"; + public static final String MESSAGE_HOMEWORK_ALREADY_MARKED_AS_DONE = + "Homework %s\nof student %s is already marked as done\n"; + public static final String MESSAGE_HOMEWORK_MARKED_AS_DONE = "Homework %s of\nstudent %s is marked as done\n"; + public static final String MESSAGE_HOMEWORK_MARKED_AS_UNDONE = "Homework %s of\nstudent %s is marked as undone\n"; + public static final String MESSAGE_HOMEWORK_ALREADY_MARKED_AS_UNDONE = + "Homework %s of\bstudent %s is already marked as undone\n"; + public static final String MESSAGE_INVALID_STUDENT_NAME = "No student found!\n"; + public static final String MESSAGE_NO_LESSON_FOUND = "No lesson is found!"; + public static final String MESSAGE_ALL_LESSONS_LISTED_OVERVIEW = "%d lessons from all students listed:\n%s"; + public static final String MESSAGE_LESSONS_LISTED_OVERVIEW = "%d lessons from %d students listed: \n%s"; + public static final String MESSAGE_LESSON_DELETED_SUCCESS = "Lesson: %s, %s\n" + + "Deleted from the student %s\n"; + public static final String MESSAGE_INVALID_LESSON_DISPLAYED_INDEX = "The lesson index provided is invalid"; + public static final String MESSAGE_INVALID_EXAM_DISPLAYED_INDEX = "The exam index provided is invalid"; + public static final String MESSAGE_EXAM_DELETED_SUCCESS = "Exam: %s, %s\n" + + "Deleted from the student %s\n"; + public static final String MESSAGE_EXAM_ADDED_SUCCESS = "New exam added: \n%s \n" + + "To the following students: \n%s"; + public static final String MESSAGE_EXAMS_LISTED_OVERVIEW = "%d exams from %d students listed: \n%s"; + public static final String MESSAGE_ALL_EXAMS_LISTED_OVERVIEW = "%d exams from all students listed:\n%s"; + public static final String MESSAGE_NO_EXAM_FOUND = "No exam is found!"; + public static final String MESSAGE_EXAM_NOT_COMPLETED = "Exam is not yet completed, a grade cannot be assigned!"; + public static final String MESSAGE_EXAM_UPDATED_SUCCESS = "Exam %s of student %s is updated to:\n" + + "Exam name: %s\n" + + "Start Time: %s\n" + + "End Time: %s\n" + + "Weightage: %s" + "%%" + "\n" + + "Grade: %s\n"; + public static final String MESSAGE_HOMEWORK_UPDATED_SUCCESS = "Homework %s of student %s is updated to:\n" + + "Homework name: %s\n" + + "Deadline: %s\n"; + public static final String MESSAGE_LESSON_UPDATED_SUCCESS = "Lesson %s of student %s is updated to:\n" + + "Lesson name: %s\n" + + "Start Time: %s\n" + + "End Time: %s\n"; + public static final String MESSAGE_HAS_DUPLICATE_NAMES = "Duplicate names detected for **%s**." + + "\nPlease enter full name(s)"; + public static final String MESSAGE_RESULT_IN_DUPLICATE = "The result of the command will result in duplicate " + + "%s.\nPlease check the name(s) entered"; + public static final String MESSAGE_NO_SUCH_STUDENT = "No student found: **%s**.\nPlease check the name entered"; + public static final String MESSAGE_INVALID_LESSON_TIME = "Start time cannot be after end time"; + public static final String MESSAGE_INVALID_LESSON_DURATION = + "The lesson duration is too short(< 30 min)/long(> 3 hours)"; + public static final String MESSAGE_INVALID_EXAM_TIME = "Exam start time cannot be after exam end time"; + public static final String MESSAGE_INVALID_EXAM_DURATION = + "The exam duration is too short(< 30 min)/long(> 3 hours)"; + public static final String MESSAGE_INVALID_DONE_INPUT = "Invalid input for done/ field. Accepted inputs:" + + "done, not done"; + public static final String MESSAGE_DEADLINE_IN_PAST = "Deadline cannot be in the past!"; + public static final String MESSAGE_ONLY_ONE_STUDENT = "Only one student name is allowed!"; + public static final String MESSAGE_EMPTY_STUDENT = "Student name cannot be empty!"; + public static final String MESSAGE_ONLY_ONE_HOMEWORK = "Only one homework name is allowed!"; + public static final String MESSAGE_EMPTY_HOMEWORK = "Homework name cannot be empty!"; + public static final String MESSAGE_ONLY_ONE_INDEX = "Only one index is allowed!"; + public static final String MESSAGE_EMPTY_INDEX = "Index cannot be empty!"; + public static final String MESSAGE_ONLY_ONE_DEADLINE = "Only one deadline is allowed!"; + public static final String MESSAGE_EMPTY_DEADLINE = "Deadline cannot be empty!"; + public static final Object MESSAGE_ONLY_ONE_STATUS = "Only one status is allowed!"; + public static final Object MESSAGE_EMPTY_STATUS = "Status cannot be empty!"; + public static final String MESSAGE_CONTAIN_STUDENT_NAME = "There is at least one existing student " + + "whose name contains \"%s\"."; + public static final String MESSAGE_EXTENDED_STUDENT_NAME = "There is at least one existing student " + + "whose name is contained in \"%s\"."; + public static final String MESSAGE_CONFLICTING_LESSON_TIME = "You already have a lesson during this time!"; + public static final String MESSAGE_CONFLICTING_EXAM_TIME = "This student has an exam during this time!"; + public static final String MESSAGE_ONLY_ONE_LESSON = "Only one lesson title is allowed"; + public static final String MESSAGE_ONLY_ONE_DATE = "Only one lesson date is allowed"; + public static final String MESSAGE_ONLY_ONE_STARTTIME = "Only one start time is allowed"; + public static final String MESSAGE_ONLY_ONE_ENDTIME = "Only one end time is allowed"; + public static final String MESSAGE_ONLY_ONE_EMAIL = "Only one email is allowed"; + public static final String MESSAGE_ONLY_ONE_ADDRESS = "Only one address is allowed"; + public static final String MESSAGE_ONLY_ONE_PHONE = "Only one phone number is allowed"; + public static final String MESSAGE_ONLY_ONE_DONE = "Only one DONE keyword is allowed"; + public static final String MESSAGE_ONLY_ONE_GRADE = "Only one exam grade is allowed"; + public static final String MESSAGE_ONLY_ONE_WEIGHTAGE = "Only one exam weightage is allowed"; + public static final String MESSAGE_ONLY_ONE_EXAM = "Only one exam title is allowed"; } diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..641d315e68d 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -8,7 +8,7 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.address.model.student.Student; /** * API of the Logic component @@ -31,7 +31,7 @@ public interface Logic { ReadOnlyAddressBook getAddressBook(); /** Returns an unmodifiable view of the filtered list of persons */ - ObservableList getFilteredPersonList(); + ObservableList getFilteredPersonList(); /** * Returns the user prefs' address book file path. diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9d9c6d15bdc..644df39fd74 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -14,7 +14,7 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.address.model.student.Student; import seedu.address.storage.Storage; /** @@ -60,8 +60,8 @@ public ReadOnlyAddressBook getAddressBook() { } @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); + public ObservableList getFilteredPersonList() { + return model.getFilteredStudentList(); } @Override diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index 71656d7c5c8..50518cf93ab 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -3,47 +3,52 @@ import static java.util.Objects.requireNonNull; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GRADELEVEL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SCHOOL; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import seedu.address.commons.core.Messages; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; -import seedu.address.model.person.Person; +import seedu.address.model.student.Student; /** - * Adds a person to the address book. + * Adds a student to the address book. */ public class AddCommand extends Command { - public static final String COMMAND_WORD = "add"; + public static final String COMMAND_WORD = "new-student"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a student to the address book. " + "Parameters: " + PREFIX_NAME + "NAME " + PREFIX_PHONE + "PHONE " + PREFIX_EMAIL + "EMAIL " + PREFIX_ADDRESS + "ADDRESS " + + "[" + PREFIX_SCHOOL + "SCHOOL] " + + "[" + PREFIX_GRADELEVEL + "GRADE LEVEL] " + "[" + PREFIX_TAG + "TAG]...\n" + "Example: " + COMMAND_WORD + " " + PREFIX_NAME + "John Doe " + PREFIX_PHONE + "98765432 " + PREFIX_EMAIL + "johnd@example.com " + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; + + PREFIX_SCHOOL + "Anglo Chinese " + + PREFIX_GRADELEVEL + "Secondary 3 "; - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; + public static final String MESSAGE_SUCCESS = "New student added: %1$s"; + public static final String MESSAGE_DUPLICATE_STUDENT = "This student already exists in the address book"; - private final Person toAdd; + private final Student toAdd; /** - * Creates an AddCommand to add the specified {@code Person} + * Creates an AddCommand to add the specified {@code Student} */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; + public AddCommand(Student student) { + requireNonNull(student); + toAdd = student; } @Override @@ -51,7 +56,17 @@ public CommandResult execute(Model model) throws CommandException { requireNonNull(model); if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); + throw new CommandException(MESSAGE_DUPLICATE_STUDENT); + } + + if (model.hasDuplicateNameAdd(toAdd.getName().toString())) { + throw new CommandException(String.format(Messages.MESSAGE_CONTAIN_STUDENT_NAME, + toAdd.getName().toString())); + } + + if (model.hasExtendedName(toAdd.getName().toString())) { + throw new CommandException(String.format(Messages.MESSAGE_EXTENDED_STUDENT_NAME, + toAdd.getName().toString())); } model.addPerson(toAdd); diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index 9c86b1fa6e4..8291af5857b 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -11,7 +11,7 @@ public class ClearCommand extends Command { public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; + public static final String MESSAGE_SUCCESS = "TutorPro has been cleared!"; @Override diff --git a/src/main/java/seedu/address/logic/commands/CommandUtil.java b/src/main/java/seedu/address/logic/commands/CommandUtil.java new file mode 100644 index 00000000000..b291ad2030c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CommandUtil.java @@ -0,0 +1,51 @@ +package seedu.address.logic.commands; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + +/** + * Contains utility methods used for commands. + */ +public class CommandUtil { + + /** + * Checks if the student name exists in the address book. + * + * @param model {@code Model} which the command should operate on. + * @throws CommandException if the student name does not exist in the address book. + */ + public static void handleNonExistName(Model model, List names) throws CommandException { + StringBuilder nonExistNames = new StringBuilder(); + for (String name : names) { + if (model.noSuchStudent(name)) { + nonExistNames.append(name).append(", "); + } + } + if (nonExistNames.length() != 0) { + nonExistNames = new StringBuilder(nonExistNames.substring(0, nonExistNames.length() - 2)); + throw new CommandException(String.format(Messages.MESSAGE_NO_SUCH_STUDENT, nonExistNames)); + } + } + + /** + * Checks if the student name exists in the address book. + * + * @param model {@code Model} which the command should operate on. + * @throws CommandException if the student name does not exist in the address book. + */ + public static void handleDuplicateName(Model model, List names) throws CommandException { + StringBuilder dupNames = new StringBuilder(); + for (String name : names) { + if (model.hasDuplicateName(name)) { + dupNames.append(name).append(", "); + } + } + if (dupNames.length() != 0) { + dupNames = new StringBuilder(dupNames.substring(0, dupNames.length() - 2)); + throw new CommandException(String.format(Messages.MESSAGE_HAS_DUPLICATE_NAMES, dupNames)); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index 02fd256acba..3402c8d81e2 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -8,7 +8,7 @@ import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; -import seedu.address.model.person.Person; +import seedu.address.model.student.Student; /** * Deletes a person identified using it's displayed index from the address book. @@ -18,11 +18,11 @@ public class DeleteCommand extends Command { public static final String COMMAND_WORD = "delete"; public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; + + ": Deletes the student identified by the index number used in the displayed student list.\n" + + "Parameters: index/INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " index/1"; - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; + public static final String MESSAGE_DELETE_STUDENT_SUCCESS = "Deleted Person: %1$s"; private final Index targetIndex; @@ -33,15 +33,19 @@ public DeleteCommand(Index targetIndex) { @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); + List lastShownList = model.getFilteredStudentList(); + + if (lastShownList.isEmpty()) { + throw new CommandException(Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); + } if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + throw new CommandException(Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); } - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); + Student personToDelete = lastShownList.get(targetIndex.getZeroBased()); model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); + return new CommandResult(String.format(MESSAGE_DELETE_STUDENT_SUCCESS, personToDelete)); } @Override diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index 7e36114902f..d5d5b4c9898 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -3,10 +3,11 @@ import static java.util.Objects.requireNonNull; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; import java.util.Collections; import java.util.HashSet; @@ -19,11 +20,11 @@ import seedu.address.commons.util.CollectionUtil; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; +import seedu.address.model.student.Address; +import seedu.address.model.student.Email; +import seedu.address.model.student.Name; +import seedu.address.model.student.Phone; +import seedu.address.model.student.Student; import seedu.address.model.tag.Tag; /** @@ -31,18 +32,19 @@ */ public class EditCommand extends Command { - public static final String COMMAND_WORD = "edit"; + public static final String COMMAND_WORD = "update-info"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " + "by the index number used in the displayed person list. " + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " + + "Parameters: " + + PREFIX_INDEX + "INDEX (must be a positive integer) " + "[" + PREFIX_NAME + "NAME] " + "[" + PREFIX_PHONE + "PHONE] " + "[" + PREFIX_EMAIL + "EMAIL] " + "[" + PREFIX_ADDRESS + "ADDRESS] " + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " 1 " + + "Example: " + COMMAND_WORD + " index/1 " + PREFIX_PHONE + "91234567 " + PREFIX_EMAIL + "johndoe@example.com"; @@ -51,55 +53,68 @@ public class EditCommand extends Command { public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; private final Index index; - private final EditPersonDescriptor editPersonDescriptor; + private final EditStudentDescriptor editStudentDescriptor; /** * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with + * @param editStudentDescriptor details to edit the person with */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { + public EditCommand(Index index, EditStudentDescriptor editStudentDescriptor) { requireNonNull(index); - requireNonNull(editPersonDescriptor); + requireNonNull(editStudentDescriptor); this.index = index; - this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); + this.editStudentDescriptor = new EditStudentDescriptor(editStudentDescriptor); } @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); + List lastShownList = model.getFilteredStudentList(); if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + throw new CommandException(Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); } - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); + Student personToEdit = lastShownList.get(index.getZeroBased()); + Student editedPerson = createEditedPerson(personToEdit, editStudentDescriptor); + String newName = editedPerson.getName().toString(); + if (!newName.equals(personToEdit.getName().toString())) { + if (model.hasDuplicateNameEdit(newName, index.getZeroBased())) { + throw new CommandException(String.format(Messages.MESSAGE_CONTAIN_STUDENT_NAME, + newName)); + } + + if (model.hasExtendedNameEdit(newName, index.getZeroBased())) { + throw new CommandException(String.format(Messages.MESSAGE_EXTENDED_STUDENT_NAME, + newName)); + } + } - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { + if (!personToEdit.isSameStudent(editedPerson) && model.hasPerson(editedPerson)) { throw new CommandException(MESSAGE_DUPLICATE_PERSON); } - model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.setStudent(personToEdit, editedPerson); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); } /** * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. + * edited with {@code editStudentDescriptor}. */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { + private static Student createEditedPerson(Student personToEdit, EditStudentDescriptor editStudentDescriptor) { assert personToEdit != null; - Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); - Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); - Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); - Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); + Name updatedName = editStudentDescriptor.getName().orElse(personToEdit.getName()); + Phone updatedPhone = editStudentDescriptor.getPhone().orElse(personToEdit.getPhone()); + Email updatedEmail = editStudentDescriptor.getEmail().orElse(personToEdit.getEmail()); + Address updatedAddress = editStudentDescriptor.getAddress().orElse(personToEdit.getAddress()); + Set updatedTags = editStudentDescriptor.getTags().orElse(personToEdit.getTags()); - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + return new Student(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags, + personToEdit.getHomeworkList(), personToEdit.getLessonsList(), personToEdit.getExamsList()); } @Override @@ -117,27 +132,27 @@ public boolean equals(Object other) { // state check EditCommand e = (EditCommand) other; return index.equals(e.index) - && editPersonDescriptor.equals(e.editPersonDescriptor); + && editStudentDescriptor.equals(e.editStudentDescriptor); } /** * Stores the details to edit the person with. Each non-empty field value will replace the * corresponding field value of the person. */ - public static class EditPersonDescriptor { + public static class EditStudentDescriptor { private Name name; private Phone phone; private Email email; private Address address; private Set tags; - public EditPersonDescriptor() {} + public EditStudentDescriptor() {} /** * Copy constructor. * A defensive copy of {@code tags} is used internally. */ - public EditPersonDescriptor(EditPersonDescriptor toCopy) { + public EditStudentDescriptor(EditStudentDescriptor toCopy) { setName(toCopy.name); setPhone(toCopy.phone); setEmail(toCopy.email); @@ -209,12 +224,12 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof EditPersonDescriptor)) { + if (!(other instanceof EditStudentDescriptor)) { return false; } // state check - EditPersonDescriptor e = (EditPersonDescriptor) other; + EditStudentDescriptor e = (EditStudentDescriptor) other; return getName().equals(e.getName()) && getPhone().equals(e.getPhone()) diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index 3dd85a8ba90..fc1184f8f9d 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -9,7 +9,7 @@ public class ExitCommand extends Command { public static final String COMMAND_WORD = "exit"; - public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; + public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Tutor Pro as requested ..."; @Override public CommandResult execute(Model model) { diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index d6b19b0a0de..a1ba93eaffc 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -4,7 +4,7 @@ import seedu.address.commons.core.Messages; import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.student.NamePredicate; /** * Finds and lists all persons in address book whose name contains any of the argument keywords. @@ -14,23 +14,23 @@ public class FindCommand extends Command { public static final String COMMAND_WORD = "find"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all students whose names contain any of " + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; + + "Parameters: name/NAME [MORE_NAMES]...\n" + + "Example: " + COMMAND_WORD + " name/alice name/bob name/charlie"; - private final NameContainsKeywordsPredicate predicate; + private final NamePredicate predicate; - public FindCommand(NameContainsKeywordsPredicate predicate) { + public FindCommand(NamePredicate predicate) { this.predicate = predicate; } @Override public CommandResult execute(Model model) { requireNonNull(model); - model.updateFilteredPersonList(predicate); + model.updateFilteredStudentList(predicate); return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + String.format(Messages.MESSAGE_STUDENTS_LISTED_OVERVIEW, model.getFilteredStudentList().size(), "")); } @Override diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 84be6ad2596..e2c74ebe3e3 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -1,7 +1,7 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; import seedu.address.model.Model; @@ -12,13 +12,13 @@ public class ListCommand extends Command { public static final String COMMAND_WORD = "list"; - public static final String MESSAGE_SUCCESS = "Listed all persons"; + public static final String MESSAGE_SUCCESS = "Listed all students"; @Override public CommandResult execute(Model model) { requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/commands/ViewProfileCommand.java b/src/main/java/seedu/address/logic/commands/ViewProfileCommand.java new file mode 100644 index 00000000000..7e86345e601 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ViewProfileCommand.java @@ -0,0 +1,94 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.Student; +import seedu.address.model.tag.Tag; + +/** + * Prints student profiles with or without name-matching + */ +public class ViewProfileCommand extends Command { + public static final String COMMAND_WORD = "view-profile"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Prints the profile of the student that matches " + + "the specified name.\n" + + "Parameters: \n" + + PREFIX_NAME + "STUDENT_NAME \n" + + "Example: " + COMMAND_WORD + " " + PREFIX_NAME + "John Doe"; + private static final Predicate SHOW_ALL_STUDENTS = student -> true; + private static final String SEPERATOR = "--------------------------------------------------\n"; + + private final List names; + private final Predicate namePredicate; + + /** + * public constructor for a ViewProfileCommand + * @param names + * @param namePredicate + */ + public ViewProfileCommand(List names, Predicate namePredicate) { + this.names = names; + this.namePredicate = namePredicate; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + + StringBuilder nonExistNames = new StringBuilder(); + for (String name : names) { + if (model.noSuchStudent(name)) { + nonExistNames.append(name).append(", "); + } + } + if (nonExistNames.length() != 0) { + nonExistNames = new StringBuilder(nonExistNames.substring(0, nonExistNames.length() - 2)); + throw new CommandException(String.format(Messages.MESSAGE_NO_SUCH_STUDENT, nonExistNames)); + } + StringBuilder dupNames = new StringBuilder(); + for (String name : names) { + if (model.hasDuplicateName(name)) { + dupNames.append(name).append(", "); + } + } + if (dupNames.length() != 0) { + dupNames = new StringBuilder(dupNames.substring(0, dupNames.length() - 2)); + throw new CommandException(String.format(Messages.MESSAGE_HAS_DUPLICATE_NAMES, dupNames)); + } + model.updateFilteredStudentList(namePredicate); + + List studentList = model.getFilteredStudentList(); + + int numberOfStudents = studentList.size(); + StringBuilder sb = new StringBuilder(); + sb.append(SEPERATOR); + + // Loop through each student and add their lesson to the string builder + for (Student student : studentList) { + sb.append(student.getName().fullName).append(":\n"); + sb.append("Phone: ").append(student.getPhone()).append("\n"); + sb.append("Address: ").append(student.getAddress().toString()).append("\n"); + sb.append("Email: ").append(student.getEmail()).append("\n"); + sb.append("Tags: "); + for (Tag tag : student.getTags()) { + sb.append(tag); + } + sb.append("\n"); + + sb.append(SEPERATOR); + } + + return new CommandResult( + String.format(Messages.MESSAGE_STUDENTS_LISTED_OVERVIEW, numberOfStudents, sb)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/exam/CreateExamCommand.java b/src/main/java/seedu/address/logic/commands/exam/CreateExamCommand.java new file mode 100644 index 00000000000..e81abf38082 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/exam/CreateExamCommand.java @@ -0,0 +1,139 @@ +package seedu.address.logic.commands.exam; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ENDTIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STARTTIME; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; + +import java.time.LocalDateTime; +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.Exam; +import seedu.address.model.student.Grade; +import seedu.address.model.student.NamePredicate; +import seedu.address.model.student.Student; + +/** + * Adds an exam to a student. + */ +public class CreateExamCommand extends Command { + + public static final String COMMAND_WORD = "new-exam"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds an exam to students.\n" + + "Parameters: " + + PREFIX_NAME + "STUDENT_NAME " + + PREFIX_EXAM + "EXAM_NAME " + + PREFIX_STARTTIME + "Start time " + + PREFIX_ENDTIME + "End time\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_EXAM + "Math MYE " + + PREFIX_STARTTIME + "2023-05-21 12:00 " + + PREFIX_ENDTIME + "2023-05-21 14:00"; + + + private final String examDescription; + private final LocalDateTime startTime; + private final LocalDateTime endTime; + private final NamePredicate predicate; + private final List names; + private final Double weightage; + private final Grade grade; + + + /** + * Creates a CreateExamCommand to add the specified exam to the specified student. + */ + public CreateExamCommand(List names, NamePredicate predicate, String examDescription, + LocalDateTime startTime, LocalDateTime endTime, Double weightage, Grade grade) { + requireNonNull(predicate); + requireNonNull(examDescription); + requireNonNull(startTime); + + this.examDescription = examDescription; + this.startTime = startTime; + this.endTime = endTime; + this.predicate = predicate; + this.names = names; + this.weightage = weightage; + this.grade = grade; + + } + + @Override + public CommandResult execute(Model model) throws CommandException { + StringBuilder lessonClashMessage = new StringBuilder("\n"); + requireNonNull(model); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + + StringBuilder nonExistNames = new StringBuilder(); + for (String name : names) { + if (model.noSuchStudent(name)) { + nonExistNames.append(name).append(", "); + } + } + if (nonExistNames.length() != 0) { + nonExistNames = new StringBuilder(nonExistNames.substring(0, nonExistNames.length() - 2)); + throw new CommandException(String.format(Messages.MESSAGE_NO_SUCH_STUDENT, nonExistNames)); + } + StringBuilder dupNames = new StringBuilder(); + for (String name : names) { + if (model.hasDuplicateName(name)) { + dupNames.append(name).append(", "); + } + } + if (dupNames.length() != 0) { + dupNames = new StringBuilder(dupNames.substring(0, dupNames.length() - 2)); + throw new CommandException(String.format(Messages.MESSAGE_HAS_DUPLICATE_NAMES, dupNames)); + } + model.updateFilteredStudentList(predicate); + + List studentList = model.getFilteredStudentList(); + + if (endTime.isAfter(LocalDateTime.now()) && grade != null) { + throw new CommandException(Messages.MESSAGE_EXAM_NOT_COMPLETED); + } + + Exam exam = new Exam(examDescription, startTime, endTime, weightage, grade); + + try { + for (Student student : studentList) { + if (student.hasLessonAtSameTime(exam)) { + lessonClashMessage.append("*******WARNING*******\n").append(student.getName().fullName) + .append(" has a lesson at the same time as the exam\n Consider rescheduling clashing lesson"); + } + student.addExam(exam); + } + } catch (Exception e) { + throw new CommandException(e.getMessage()); + } + + StringBuilder sb = new StringBuilder(); + for (Student student : studentList) { + sb.append(student.getName().fullName); + sb.append("\n"); + } + + return new CommandResult( + String.format(Messages.MESSAGE_EXAM_ADDED_SUCCESS, exam, sb) + lessonClashMessage); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CreateExamCommand // instanceof handles nulls + && predicate.equals(((CreateExamCommand) other).predicate) + && startTime.equals(((CreateExamCommand) other).startTime) + && endTime.equals(((CreateExamCommand) other).endTime) + && examDescription.equals(((CreateExamCommand) other).examDescription)); + } +} + diff --git a/src/main/java/seedu/address/logic/commands/exam/DeleteExamCommand.java b/src/main/java/seedu/address/logic/commands/exam/DeleteExamCommand.java new file mode 100644 index 00000000000..f22c0f6c939 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/exam/DeleteExamCommand.java @@ -0,0 +1,109 @@ +package seedu.address.logic.commands.exam; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.NamePredicate; +import seedu.address.model.student.Student; + +/** + * Deletes an exam from a student. + */ +public class DeleteExamCommand extends Command { + + public static final String COMMAND_WORD = "delete-exam"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes an exam from a student.\n" + + "Parameters: " + + PREFIX_NAME + "STUDENT_NAME " + + PREFIX_INDEX + "INDEX\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_INDEX + "1"; + + private final NamePredicate predicate; + private final Index targetIndex; + private final List names; + + + /** + * Creates a DeleteExamCommand to delete the specified exam from the specified student. + */ + public DeleteExamCommand(List inputNames, NamePredicate predicate, Index targetIndex) { + requireNonNull(predicate); + requireNonNull(targetIndex); + + this.names = inputNames; + this.predicate = predicate; + this.targetIndex = targetIndex; + + } + + /** + * Executes the command and returns the result message. + * + * @param model {@code Model} which the command should operate on. + * @return feedback message of the operation result for display + * @throws CommandException if the command's preconditions are not met + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + + StringBuilder nonExistNames = new StringBuilder(); + for (String name : names) { + if (model.noSuchStudent(name)) { + nonExistNames.append(name).append(", "); + } + } + if (nonExistNames.length() != 0) { + nonExistNames = new StringBuilder(nonExistNames.substring(0, nonExistNames.length() - 2)); + throw new CommandException(String.format(Messages.MESSAGE_NO_SUCH_STUDENT, nonExistNames)); + } + StringBuilder dupNames = new StringBuilder(); + for (String name : names) { + if (model.hasDuplicateName(name)) { + dupNames.append(name).append(", "); + } + } + if (dupNames.length() != 0) { + dupNames = new StringBuilder(dupNames.substring(0, dupNames.length() - 2)); + throw new CommandException(String.format(Messages.MESSAGE_HAS_DUPLICATE_NAMES, dupNames)); + } + model.updateFilteredStudentList(predicate); + + List studentList = model.getFilteredStudentList(); + + StringBuilder sb = new StringBuilder(); + for (Student student : studentList) { + try { + sb.append(String.format(Messages.MESSAGE_EXAM_DELETED_SUCCESS, targetIndex.getOneBased(), + student.getExam(targetIndex).toString(), student.getName().toString())); + sb.append("\n"); + student.deleteExam(targetIndex); + } catch (IndexOutOfBoundsException e) { + throw new CommandException(Messages.MESSAGE_INVALID_EXAM_DISPLAYED_INDEX); + } + } + + return new CommandResult(sb.toString()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteExamCommand // instanceof handles nulls + && predicate.equals(((DeleteExamCommand) other).predicate)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/exam/UpdateExamCommand.java b/src/main/java/seedu/address/logic/commands/exam/UpdateExamCommand.java new file mode 100644 index 00000000000..039c95df267 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/exam/UpdateExamCommand.java @@ -0,0 +1,150 @@ +package seedu.address.logic.commands.exam; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ENDTIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GRADE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STARTTIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_WEIGHT; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.Exam; +import seedu.address.model.student.Grade; +import seedu.address.model.student.NamePredicate; +import seedu.address.model.student.Student; + +/** + * Updates the information of an existing exam. + */ +public class UpdateExamCommand extends Command { + + public static final String COMMAND_WORD = "update-exam"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Update the information of an existing exam.\n" + + "Parameters: " + + PREFIX_NAME + "STUDENT_NAME " + + PREFIX_INDEX + "EXAM_INDEX " + + PREFIX_EXAM + "EXAM_NAME " + + PREFIX_STARTTIME + "START TIME" + + PREFIX_ENDTIME + "END TIME" + + PREFIX_WEIGHT + "WEIGHTAGE" + + PREFIX_GRADE + "GRADE\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_INDEX + "1 " + + PREFIX_EXAM + "Math Exam "; + + private final Index index; + private final Optional examName; + private final Optional startTime; + private final Optional endTime; + private final Optional weightage; + private final Optional grade; + + private final NamePredicate predicate; + private final List names; + + /** + * Creates an UpdateExamCommand to update the specified exam of the specified student. + */ + public UpdateExamCommand(List names, Index index, NamePredicate predicate, + Optional examName, Optional startTime, + Optional endTime, Optional weightage, + Optional grade) { + requireNonNull(predicate); + requireNonNull(index); + + this.index = index; + this.predicate = predicate; + this.examName = examName; + this.startTime = startTime; + this.endTime = endTime; + this.names = names; + this.weightage = weightage; + this.grade = grade; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + + StringBuilder nonExistNames = new StringBuilder(); + for (String name : names) { + if (model.noSuchStudent(name)) { + nonExistNames.append(name).append(", "); + } + } + if (nonExistNames.length() != 0) { + nonExistNames = new StringBuilder(nonExistNames.substring(0, nonExistNames.length() - 2)); + throw new CommandException(String.format(Messages.MESSAGE_NO_SUCH_STUDENT, nonExistNames)); + } + StringBuilder dupNames = new StringBuilder(); + for (String name : names) { + if (model.hasDuplicateName(name)) { + dupNames.append(name).append(", "); + } + } + if (dupNames.length() != 0) { + dupNames = new StringBuilder(dupNames.substring(0, dupNames.length() - 2)); + throw new CommandException(String.format(Messages.MESSAGE_HAS_DUPLICATE_NAMES, dupNames)); + } + model.updateFilteredStudentList(predicate); + List studentList = model.getFilteredStudentList(); + + Student student = studentList.get(0); + Exam examToUpdate; + try { + examToUpdate = student.getExam(index); + } catch (IndexOutOfBoundsException e) { + throw new CommandException(Messages.MESSAGE_INVALID_EXAM_DISPLAYED_INDEX); + } + + String newExamName = this.examName.orElse(examToUpdate.getDescription()); + LocalDateTime newStartTime = this.startTime.orElse(examToUpdate.getStartTime()); + LocalDateTime newEndTime = this.endTime.orElse(examToUpdate.getEndTime()); + if (newStartTime.isAfter(newEndTime)) { + throw new CommandException(Messages.MESSAGE_INVALID_EXAM_TIME); + } + if (newEndTime.isAfter(LocalDateTime.now()) && grade.isPresent()) { + throw new CommandException(Messages.MESSAGE_EXAM_NOT_COMPLETED); + } + Double newWeightage = this.weightage.orElse(examToUpdate.getWeightage()); + Grade newGrade = this.grade.orElse(examToUpdate.getGrade()); + Exam newExam = new Exam(newExamName, newStartTime, newEndTime, newWeightage, newGrade); + + try { + student.setExam(index.getZeroBased(), newExam); + } catch (Exception e) { + throw new CommandException(e.getMessage()); + } + model.updateFilteredStudentList(s -> s == student); + return new CommandResult( + String.format(Messages.MESSAGE_EXAM_UPDATED_SUCCESS, index.getOneBased(), + student.getName().getFirstName(), + newExamName, newStartTime, newEndTime, newWeightage, newGrade)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UpdateExamCommand // instanceof handles nulls + && predicate.equals(((UpdateExamCommand) other).predicate) + && index == ((UpdateExamCommand) other).index + && examName.equals(((UpdateExamCommand) other).examName) + && startTime.equals(((UpdateExamCommand) other).startTime) + && endTime.equals((((UpdateExamCommand) other).endTime))); + } +} diff --git a/src/main/java/seedu/address/logic/commands/exam/ViewExamCommand.java b/src/main/java/seedu/address/logic/commands/exam/ViewExamCommand.java new file mode 100644 index 00000000000..a2002dc6d50 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/exam/ViewExamCommand.java @@ -0,0 +1,174 @@ +package seedu.address.logic.commands.exam; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.Exam; +import seedu.address.model.student.Student; + +/** + * Finds and lists all exams in address book whose name contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class ViewExamCommand extends Command { + + public static final String COMMAND_WORD = "view-exam"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all exams filtered by\n" + + "* name of student (case-insensitive) and/or\n" + + "* date and/or\n" + + "* exam name and/or\n" + + "* whether it is done\n" + + "and displays them as a list with index numbers.\n" + + "Parameters: " + + PREFIX_NAME + "STUDENT_NAME " + + PREFIX_DATE + "DATE " + + PREFIX_EXAM + "EXAM NAME " + + PREFIX_DONE + "COMPLETED KEYWORD\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_DATE + "2023-05-21 " + + PREFIX_EXAM + "Math MYE " + + PREFIX_DONE + "done(or not done)"; + private static final Predicate SHOW_ALL_EXAMS = exam -> true; + private final Predicate namePredicate; + private final Predicate examDatePredicate; + private final Predicate examNamePredicate; + private final Predicate donePredicate; + private final boolean defaultPredicateFlag; + private final List names; + + + /** + * Overloaded constructor for ViewExamCommand. + * + * @param namePredicate Predicate to filter students by name. + * @param examNamePredicate Predicate to filter exams by exam name. + * @param donePredicate Predicate to filter exams by whether it is done. + * @param defaultPredicateFlag Flag to indicate if the default predicate is used. + */ + public ViewExamCommand(List names, Predicate namePredicate, Predicate examNamePredicate, + Predicate donePredicate, boolean defaultPredicateFlag) { + this.namePredicate = namePredicate; + this.examDatePredicate = SHOW_ALL_EXAMS; + this.defaultPredicateFlag = defaultPredicateFlag; + this.examNamePredicate = examNamePredicate; + this.donePredicate = donePredicate; + this.names = names; + } + + /** + * Overloaded constructor for ViewExamCommand. + * + * @param namePredicate Predicate to filter students by name. + * @param examDatePredicate Predicate to filter exams by date. + * @param examNamePredicate Predicate to filter exams by exam name. + * @param donePredicate Predicate to filter exams by whether it is done. + * @param defaultPredicateFlag Flag to indicate if the default predicate is used. + */ + public ViewExamCommand(List names, Predicate namePredicate, Predicate examDatePredicate, + Predicate examNamePredicate, Predicate donePredicate, + boolean defaultPredicateFlag) { + this.namePredicate = namePredicate; + this.examDatePredicate = examDatePredicate; + this.defaultPredicateFlag = defaultPredicateFlag; + this.examNamePredicate = examNamePredicate; + this.donePredicate = donePredicate; + this.names = names; + } + + /** + * Executes the view-exam command and returns the result message. + * + * @param model {@code Model} which the command should operate on. + * @return feedback message of the operation result for display. + * @throws CommandException if an error occurs during command execution. + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + + StringBuilder nonExistNames = new StringBuilder(); + for (String name : names) { + if (model.noSuchStudent(name)) { + nonExistNames.append(name).append(", "); + } + } + if (nonExistNames.length() != 0) { + nonExistNames = new StringBuilder(nonExistNames.substring(0, nonExistNames.length() - 2)); + throw new CommandException(String.format(Messages.MESSAGE_NO_SUCH_STUDENT, nonExistNames)); + } + StringBuilder dupNames = new StringBuilder(); + for (String name : names) { + if (model.hasDuplicateName(name)) { + dupNames.append(name).append(", "); + } + } + if (dupNames.length() != 0) { + dupNames = new StringBuilder(dupNames.substring(0, dupNames.length() - 2)); + throw new CommandException(String.format(Messages.MESSAGE_HAS_DUPLICATE_NAMES, dupNames)); + } + model.updateFilteredStudentList(namePredicate); + + List studentList = model.getFilteredStudentList(); + + int numberOfStudents = studentList.size(); + int numOfLessons = 0; + StringBuilder sb = new StringBuilder(); + sb.append("--------------------------------------------------\n"); + + // Loop through each student and add their lesson to the string builder + for (Student student : studentList) { + List examList = student.getFilteredExamList(examDatePredicate, examNamePredicate, + donePredicate); + if (!examList.isEmpty()) { + sb.append(student.getName().fullName).append(":\n"); + + numOfLessons += examList.size(); + + for (int i = 0; i < examList.size(); i++) { + sb.append(i + 1).append(". ").append(examList.get(i)).append("\n"); + } + + sb.append("--------------------------------------------------\n"); + } + } + + // If no homework is found, throw an exception + if (numOfLessons == 0) { + throw new CommandException(Messages.MESSAGE_NO_EXAM_FOUND); + } + + // If the default predicate is used, display a different message + if (defaultPredicateFlag) { + return new CommandResult( + String.format(Messages.MESSAGE_ALL_EXAMS_LISTED_OVERVIEW, numOfLessons, sb)); + } else { + return new CommandResult( + String.format(Messages.MESSAGE_EXAMS_LISTED_OVERVIEW, numOfLessons, numberOfStudents, sb)); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ViewExamCommand // instanceof handles nulls + && namePredicate.equals(((ViewExamCommand) other).namePredicate) + && examDatePredicate.equals(((ViewExamCommand) other).examDatePredicate) + && defaultPredicateFlag == ((ViewExamCommand) other).defaultPredicateFlag); + } +} + diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java index a16bd14f2cd..f44c0c19186 100644 --- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java +++ b/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java @@ -1,5 +1,7 @@ package seedu.address.logic.commands.exceptions; +import seedu.address.logic.commands.Command; + /** * Represents an error which occurs during execution of a {@link Command}. */ diff --git a/src/main/java/seedu/address/logic/commands/homework/CreateHomeworkCommand.java b/src/main/java/seedu/address/logic/commands/homework/CreateHomeworkCommand.java new file mode 100644 index 00000000000..4190d675e8b --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/homework/CreateHomeworkCommand.java @@ -0,0 +1,120 @@ +package seedu.address.logic.commands.homework; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.commands.CommandUtil.handleDuplicateName; +import static seedu.address.logic.commands.CommandUtil.handleNonExistName; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_HOMEWORK; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; + +import java.time.LocalDateTime; +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.Homework; +import seedu.address.model.student.NamePredicate; +import seedu.address.model.student.Student; +import seedu.address.model.student.exceptions.DuplicateEntryException; + +/** + * Adds an assignment to a student. + */ +public class CreateHomeworkCommand extends Command { + public static final String COMMAND_WORD = "new-homework"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds an assignment to students.\n" + + "Parameters: " + + PREFIX_NAME + "STUDENT_NAME " + + PREFIX_HOMEWORK + "HOMEWORK_NAME " + + PREFIX_DEADLINE + "DEADLINE\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_HOMEWORK + "Math Homework " + + PREFIX_DEADLINE + "2023-03-01T12:00"; + + private final String homeworkName; + private final LocalDateTime deadline; + private final NamePredicate predicate; + private final List names; + + /** + * Creates a CreateHomeworkCommand to add the specified assignment to the specified student. + */ + public CreateHomeworkCommand(List names, NamePredicate predicate, String homeworkName, + LocalDateTime deadline) { + requireNonNull(homeworkName); + requireNonNull(deadline); + requireNonNull(predicate); + + this.homeworkName = homeworkName; + this.deadline = deadline; + this.predicate = predicate; + this.names = names; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + + handleNonExistName(model, names); + handleDuplicateName(model, names); + model.updateFilteredStudentList(predicate); + + List studentList = model.getFilteredStudentList(); + Homework homework = new Homework(homeworkName, deadline); + addHomework(studentList, homework); + String message = formatMessage(studentList, homework); + + return new CommandResult( + String.format(Messages.MESSAGE_HOMEWORK_ADDED_SUCCESS, homework, message)); + } + + /** + * Adds the homework to the students. + * + * @param studentList The list of students to add the homework to. + * @param homework The homework to be added. + * @throws DuplicateEntryException If the homework already exists in the student. + * @throws CommandException If the homework already exists in the student. + */ + public void addHomework(List studentList, Homework homework) + throws DuplicateEntryException, CommandException { + try { + for (Student student : studentList) { + student.addHomework(homework); + } + } catch (DuplicateEntryException e) { + throw new CommandException(String.format(Messages.MESSAGE_RESULT_IN_DUPLICATE, "homework")); + } + } + + /** + * Formats the message to be displayed to the user. + * + * @param studentList The list of students to add the homework to. + * @param homework The homework to be added. + * @return The formatted message. + */ + public String formatMessage(List studentList, Homework homework) { + StringBuilder sb = new StringBuilder(); + for (Student student : studentList) { + sb.append(student.getName().fullName); + sb.append("\n"); + } + return sb.toString(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CreateHomeworkCommand // instanceof handles nulls + && predicate.equals(((CreateHomeworkCommand) other).predicate) + && homeworkName.equals(((CreateHomeworkCommand) other).homeworkName) + && deadline.equals(((CreateHomeworkCommand) other).deadline)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/homework/DeleteHomeworkCommand.java b/src/main/java/seedu/address/logic/commands/homework/DeleteHomeworkCommand.java new file mode 100644 index 00000000000..5c274b40944 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/homework/DeleteHomeworkCommand.java @@ -0,0 +1,110 @@ +package seedu.address.logic.commands.homework; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.commands.CommandUtil.handleDuplicateName; +import static seedu.address.logic.commands.CommandUtil.handleNonExistName; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.NamePredicate; +import seedu.address.model.student.Student; + +/** + * Deletes an assignment from a student. + */ +public class DeleteHomeworkCommand extends Command { + public static final String COMMAND_WORD = "delete-homework"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes an assignment from a student.\n" + + "Parameters: " + + PREFIX_NAME + "STUDENT_NAME " + + PREFIX_INDEX + "INDEX\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_INDEX + "1"; + + private final NamePredicate predicate; + private final Index targetIndex; + private final List names; + /** + * Creates a DeleteHomeworkCommand to delete the specified assignment from the specified student. + */ + public DeleteHomeworkCommand(List names, NamePredicate predicate, Index targetIndex) { + requireNonNull(predicate); + requireNonNull(targetIndex); + + this.predicate = predicate; + this.targetIndex = targetIndex; + this.names = names; + } + + /** + * Executes the command and returns the result message. + * + * @param model {@code Model} which the command should operate on. + * @return feedback message of the operation result for display + * @throws CommandException if the command's preconditions are not met + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + + handleNonExistName(model, names); + handleDuplicateName(model, names); + model.updateFilteredStudentList(predicate); + + List studentList = model.getFilteredStudentList(); + + String message = formatMessage(studentList); + return new CommandResult(message); + } + + /** + * Formats the string to be displayed. + * + * @param sb StringBuilder to be formatted. + * @param student Student to be formatted. + * @throws CommandException if the command's preconditions are not met + */ + public void removeHomework(StringBuilder sb, Student student) throws CommandException { + try { + sb.append(String.format(Messages.MESSAGE_HOMEWORK_DELETED_SUCCESS, targetIndex.getOneBased(), + student.getHomework(targetIndex).toString(), student.getName().toString())); + sb.append("\n"); + student.deleteHomework(targetIndex); + } catch (IndexOutOfBoundsException e) { + throw new CommandException(Messages.MESSAGE_INVALID_HOMEWORK_DISPLAYED_INDEX); + } + } + + /** + * Formats the string to be displayed. + * + * @param studentList List of students to be formatted. + * @return String to be displayed. + * @throws CommandException if the command's preconditions are not met + */ + public String formatMessage(List studentList) throws CommandException { + StringBuilder sb = new StringBuilder(); + for (Student student : studentList) { + removeHomework(sb, student); + } + return sb.toString(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteHomeworkCommand // instanceof handles nulls + && predicate.equals(((DeleteHomeworkCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/homework/MarkHomeworkAsDoneCommand.java b/src/main/java/seedu/address/logic/commands/homework/MarkHomeworkAsDoneCommand.java new file mode 100644 index 00000000000..73ddf5634ae --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/homework/MarkHomeworkAsDoneCommand.java @@ -0,0 +1,133 @@ +package seedu.address.logic.commands.homework; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.commands.CommandUtil.handleDuplicateName; +import static seedu.address.logic.commands.CommandUtil.handleNonExistName; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.Homework; +import seedu.address.model.student.NamePredicate; +import seedu.address.model.student.Student; + +/** + * Marks an assignment as done for a student. + */ +public class MarkHomeworkAsDoneCommand extends Command { + public static final String COMMAND_WORD = "mark-homework"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Marks an assignment as done for a student.\n" + + "Parameters: " + + PREFIX_NAME + "STUDENT_NAME " + + PREFIX_INDEX + "INDEX\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_INDEX + "1"; + + private final NamePredicate predicate; + private final Index targetIndex; + private final List names; + + /** + * Creates a MarkHomeworkAsDoneCommand to mark the specified assignment as done for the specified student. + */ + public MarkHomeworkAsDoneCommand(List names, NamePredicate predicate, Index targetIndex) { + requireNonNull(predicate); + requireNonNull(targetIndex); + + this.predicate = predicate; + this.targetIndex = targetIndex; + this.names = names; + } + + /** + * Executes the command and returns the result message. + * + * @param model {@code Model} which the command should operate on. + * @return feedback message of the operation result for display + * @throws CommandException if the command's preconditions are not met + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + + handleNonExistName(model, names); + handleDuplicateName(model, names); + model.updateFilteredStudentList(predicate); + + List studentList = model.getFilteredStudentList(); + String message = formatString(studentList); + + return new CommandResult(message); + } + + /** + * Formats the string to be displayed to the user. + * + * @param studentList List of students to be displayed. + * @return String to be displayed to the user. + * @throws CommandException if the command's preconditions are not met + */ + public String formatString(List studentList) throws CommandException { + if (studentList.isEmpty()) { + throw new CommandException(Messages.MESSAGE_INVALID_STUDENT_NAME); + } + + StringBuilder sb = new StringBuilder(); + for (Student student : studentList) { + markHomework(sb, student); + } + return sb.toString(); + } + + /** + * Marks the homework as done for the student. + * + * @param sb String builder to be displayed to the user. + * @param student Student to be marked as done. + * @throws CommandException if the command's preconditions are not met + */ + public void markHomework(StringBuilder sb, Student student) throws CommandException { + try { + Homework homeworkToMarkAsDone = student.getHomework(targetIndex); + differentiateHomework(sb, homeworkToMarkAsDone, student); + sb.append("\n"); + } catch (IndexOutOfBoundsException e) { + throw new CommandException(Messages.MESSAGE_INVALID_HOMEWORK_DISPLAYED_INDEX); + } + } + + /** + * Differentiates between the homework being marked as done and the homework already being marked as done. + * + * @param sb String builder to be displayed to the user. + * @param homeworkToMarkAsDone Homework to be marked as done. + * @param student Student to be marked as done. + */ + public void differentiateHomework(StringBuilder sb, Homework homeworkToMarkAsDone, Student student) { + if (homeworkToMarkAsDone.isCompleted()) { + sb.append(String.format(Messages.MESSAGE_HOMEWORK_ALREADY_MARKED_AS_DONE, + homeworkToMarkAsDone.getDescription(), student.getName().toString())); + } else { + sb.append(String.format(Messages.MESSAGE_HOMEWORK_MARKED_AS_DONE, + homeworkToMarkAsDone.getDescription(), student.getName().toString())); + student.markHomeworkAsDone(targetIndex); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof MarkHomeworkAsDoneCommand // instanceof handles nulls + && predicate.equals(((MarkHomeworkAsDoneCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/homework/MarkHomeworkAsUndoCommand.java b/src/main/java/seedu/address/logic/commands/homework/MarkHomeworkAsUndoCommand.java new file mode 100644 index 00000000000..37809b52b41 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/homework/MarkHomeworkAsUndoCommand.java @@ -0,0 +1,133 @@ +package seedu.address.logic.commands.homework; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.commands.CommandUtil.handleDuplicateName; +import static seedu.address.logic.commands.CommandUtil.handleNonExistName; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.Homework; +import seedu.address.model.student.NamePredicate; +import seedu.address.model.student.Student; + +/** + * Marks a homework as undone for a student. + */ +public class MarkHomeworkAsUndoCommand extends Command { + public static final String COMMAND_WORD = "unmark-homework"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Marks a homework as undone for a student.\n" + + "Parameters: " + + PREFIX_NAME + "STUDENT_NAME " + + PREFIX_INDEX + "INDEX\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_INDEX + "1"; + private final NamePredicate predicate; + private final Index targetIndex; + private final List names; + + /** + * Creates a MarkHomeworkAsUndoCommand to mark the specified homework as undone for the specified student. + */ + public MarkHomeworkAsUndoCommand(List names, NamePredicate predicate, Index targetIndex) { + requireNonNull(predicate); + requireNonNull(targetIndex); + + this.predicate = predicate; + this.targetIndex = targetIndex; + this.names = names; + } + + /** + * Executes the command and returns the result message. + * + * @param model {@code Model} which the command should operate on. + * @return feedback message of the operation result for display + * @throws CommandException if the command's preconditions are not met + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + + handleNonExistName(model, names); + handleDuplicateName(model, names); + model.updateFilteredStudentList(predicate); + + List studentList = model.getFilteredStudentList(); + String message = formatString(studentList); + + return new CommandResult(message); + } + + /** + * Formats the string to be returned by the command. + * + * @param studentList List of students to be formatted. + * @return String to be returned by the command. + * @throws CommandException if the command's preconditions are not met + */ + public String formatString(List studentList) throws CommandException { + if (studentList.isEmpty()) { + throw new CommandException(Messages.MESSAGE_INVALID_STUDENT_NAME); + } + + StringBuilder sb = new StringBuilder(); + for (Student student : studentList) { + unmarkHomework(sb, student); + } + + return sb.toString(); + } + + /** + * Marks the homework as undone for the specified student. + * + * @param sb StringBuilder to append the result message. + * @param student Student to be marked as undone. + * @throws CommandException if the command's preconditions are not met + */ + public void unmarkHomework(StringBuilder sb, Student student) throws CommandException { + try { + Homework homeworkToMarkAsUndone = student.getHomework(targetIndex); + differentiateHomework(sb, homeworkToMarkAsUndone, student); + } catch (IndexOutOfBoundsException e) { + throw new CommandException(Messages.MESSAGE_INVALID_HOMEWORK_DISPLAYED_INDEX); + } + } + + /** + * Differentiates between marking a homework as undone for a student who has not completed the homework + * and marking a homework as undone for a student who has completed the homework. + * + * @param sb StringBuilder to append the result message. + * @param homeworkToMarkAsUndone Homework to be marked as undone. + * @param student Student to be marked as undone. + */ + public void differentiateHomework(StringBuilder sb, Homework homeworkToMarkAsUndone, Student student) { + if (!homeworkToMarkAsUndone.isCompleted()) { + sb.append(String.format(Messages.MESSAGE_HOMEWORK_ALREADY_MARKED_AS_UNDONE, + homeworkToMarkAsUndone.getDescription(), student.getName().toString())); + } else { + sb.append(String.format(Messages.MESSAGE_HOMEWORK_MARKED_AS_UNDONE, + homeworkToMarkAsUndone.getDescription(), student.getName().toString())); + student.markHomeworkAsUndone(targetIndex); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof MarkHomeworkAsUndoCommand // instanceof handles nulls + && predicate.equals(((MarkHomeworkAsUndoCommand) other).predicate)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/homework/UpdateHomeworkCommand.java b/src/main/java/seedu/address/logic/commands/homework/UpdateHomeworkCommand.java new file mode 100644 index 00000000000..c6730931686 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/homework/UpdateHomeworkCommand.java @@ -0,0 +1,129 @@ +package seedu.address.logic.commands.homework; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.commands.CommandUtil.handleDuplicateName; +import static seedu.address.logic.commands.CommandUtil.handleNonExistName; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_HOMEWORK; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.Homework; +import seedu.address.model.student.NamePredicate; +import seedu.address.model.student.Student; +import seedu.address.model.student.exceptions.DuplicateEntryException; + +/** + * Update the information of an existing homework. + */ +public class UpdateHomeworkCommand extends Command { + public static final String COMMAND_WORD = "update-homework"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Update the information of an existing homework.\n" + + "Parameters: " + + PREFIX_NAME + "STUDENT_NAME " + + PREFIX_INDEX + "HOMEWORK_INDEX " + + PREFIX_HOMEWORK + "HOMEWORK_NAME " + + PREFIX_DEADLINE + "DEADLINE\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_INDEX + "1 " + + PREFIX_HOMEWORK + "Math Homework "; + private static final DateTimeFormatter PRINT_FORMATTER = DateTimeFormatter.ofPattern("dd MMM yyyy HH:mm"); + private final Index index; + private final Optional homeworkName; + private final Optional deadline; + private final NamePredicate predicate; + private final List names; + + /** + * Creates an UpdateHomeworkCommand to update the information of an existing homework. + * + * @param index of the homework in the filtered homework list to update + * @param predicate of the student to update the homework + * @param homeworkName of the homework to be updated to + * @param deadline of the homework to be updated to + */ + public UpdateHomeworkCommand(List names, Index index, NamePredicate predicate, + Optional homeworkName, Optional deadline) { + requireNonNull(predicate); + requireNonNull(index); + + this.index = index; + this.predicate = predicate; + this.homeworkName = homeworkName; + this.deadline = deadline; + this.names = names; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + + handleNonExistName(model, names); + handleDuplicateName(model, names); + model.updateFilteredStudentList(predicate); + + List studentList = model.getFilteredStudentList(); + Student student = studentList.get(0); + + try { + Homework homeworkToUpdate = student.getHomework(index); + String newHomeworkName = this.homeworkName.orElse(homeworkToUpdate.getDescription()); + LocalDateTime newDeadline = this.deadline.orElse(homeworkToUpdate.getDeadline()); + Homework newHomework = new Homework(newHomeworkName, newDeadline); + if (homeworkToUpdate.isCompleted()) { + newHomework.markAsDone(); + } + updateHomework(student, homeworkToUpdate, newHomework); + + return new CommandResult( + String.format(Messages.MESSAGE_HOMEWORK_UPDATED_SUCCESS, index.getOneBased(), + student.getName().getFirstName(), newHomeworkName, newDeadline.format(PRINT_FORMATTER))); + } catch (IndexOutOfBoundsException e) { + throw new CommandException(Messages.MESSAGE_INVALID_HOMEWORK_DISPLAYED_INDEX); + } + } + + /** + * Updates the homework of the student. + * + * @param student to update the homework + * @param homeworkToUpdate to be updated + * @param newHomework to be updated to + * @throws DuplicateEntryException if the homework to be updated to already exists + * @throws CommandException if the homework to be updated does not exist + */ + public void updateHomework(Student student, Homework homeworkToUpdate, Homework newHomework) + throws DuplicateEntryException, CommandException { + try { + student.setHomework(homeworkToUpdate, newHomework); + } catch (IndexOutOfBoundsException e) { + throw new CommandException(Messages.MESSAGE_INVALID_HOMEWORK_DISPLAYED_INDEX); + } catch (DuplicateEntryException e) { + throw new CommandException(String.format(Messages.MESSAGE_RESULT_IN_DUPLICATE, "homework")); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UpdateHomeworkCommand // instanceof handles nulls + && predicate.equals(((UpdateHomeworkCommand) other).predicate) + && index == ((UpdateHomeworkCommand) other).index + && homeworkName.equals(((UpdateHomeworkCommand) other).homeworkName) + && deadline.equals(((UpdateHomeworkCommand) other).deadline)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/homework/ViewHomeworkCommand.java b/src/main/java/seedu/address/logic/commands/homework/ViewHomeworkCommand.java new file mode 100644 index 00000000000..04b0d653343 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/homework/ViewHomeworkCommand.java @@ -0,0 +1,145 @@ +package seedu.address.logic.commands.homework; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.commands.CommandUtil.handleDuplicateName; +import static seedu.address.logic.commands.CommandUtil.handleNonExistName; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STATUS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.Homework; +import seedu.address.model.student.Student; + +/** + * Finds and lists all homework in the homework tracker that match the given name and status keywords. + * Displays a list of homework with the ability to filter by student name and homework status. + * Keyword matching is case-insensitive. + */ +public class ViewHomeworkCommand extends Command { + public static final String COMMAND_WORD = "view-homework"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all homework that match the specified " + + "name and status keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: " + + PREFIX_NAME + "STUDENT_NAME " + + PREFIX_STATUS + "STATUS\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_STATUS + "pending"; + private static final String SEPERATOR = "--------------------------------------------------\n"; + private static final String DOT = ". "; + private static final String LINE_BREAK = "\n"; + private static final String NAME_LABEL = "%s:\n"; + private static final Predicate SHOW_ALL_HOMEWORK = homework -> true; + private final Predicate namePredicate; + private final Predicate homeworkStatusPredicate; + private final boolean defaultPredicateFlag; + private final List names; + + + /** + * Overloaded constructor for ViewHomeworkCommand. + * + * @param namePredicate Predicate to filter students by name. + */ + public ViewHomeworkCommand(List names, Predicate namePredicate, boolean defaultPredicateFlag) { + this.namePredicate = namePredicate; + this.homeworkStatusPredicate = SHOW_ALL_HOMEWORK; + this.defaultPredicateFlag = defaultPredicateFlag; + this.names = names; + } + + /** + * Overloaded constructor for ViewHomeworkCommand. + * + * @param namePredicate Predicate to filter students by name. + * @param homeworkStatusPredicate Predicate to filter homework by status. + */ + public ViewHomeworkCommand(List names, Predicate namePredicate, + Predicate homeworkStatusPredicate, boolean defaultPredicateFlag) { + this.namePredicate = namePredicate; + this.homeworkStatusPredicate = homeworkStatusPredicate; + this.defaultPredicateFlag = defaultPredicateFlag; + this.names = names; + } + + /** + * Executes the view-homework command and returns the result message. + * + * @param model {@code Model} which the command should operate on. + * @return feedback message of the operation result for display + * @throws CommandException if an error occurs during command execution. + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + + handleNonExistName(model, names); + handleDuplicateName(model, names); + model.updateFilteredStudentList(namePredicate); + + List studentList = model.getFilteredStudentList(); + + int numberOfHomework = 0; + StringBuilder sb = new StringBuilder(); + + for (Student student : studentList) { + List homeworkList = student.getFilteredHomeworkList(homeworkStatusPredicate); + if (!homeworkList.isEmpty()) { + sb.append(SEPERATOR); + sb.append(String.format(NAME_LABEL, student.getName())); + numberOfHomework += homeworkList.size(); + formatHomeworkList(homeworkList, sb); + } + } + + handleNoHomework(numberOfHomework); + return new CommandResult( + String.format(Messages.MESSAGE_ALL_HOMEWORK_LISTED_OVERVIEW, numberOfHomework, sb.toString())); + } + + /** + * Formats the homework list into a string. + * + * @param homeworkList List of homework to be formatted. + * @param sb StringBuilder to append the formatted homework list to. + */ + public void formatHomeworkList(List homeworkList, StringBuilder sb) { + for (int i = 0; i < homeworkList.size(); i++) { + sb.append(i + 1); + sb.append(DOT); + sb.append(homeworkList.get(i).toString()); + sb.append(LINE_BREAK); + } + } + + /** + * Handles the case where no homework is found. + * + * @param numberOfHomework Number of homework found. + * @throws CommandException If no homework is found. + */ + public void handleNoHomework(int numberOfHomework) throws CommandException { + // If no homework is found, throw an exception + if (numberOfHomework == 0) { + throw new CommandException(Messages.MESSAGE_NO_HOMEWORK_FOUND); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ViewHomeworkCommand // instanceof handles nulls + && namePredicate.equals(((ViewHomeworkCommand) other).namePredicate) + && homeworkStatusPredicate.equals(((ViewHomeworkCommand) other).homeworkStatusPredicate) + && defaultPredicateFlag == ((ViewHomeworkCommand) other).defaultPredicateFlag); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/lesson/CreateLessonCommand.java b/src/main/java/seedu/address/logic/commands/lesson/CreateLessonCommand.java new file mode 100644 index 00000000000..649c58c7664 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/lesson/CreateLessonCommand.java @@ -0,0 +1,145 @@ +package seedu.address.logic.commands.lesson; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ENDTIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LESSON; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STARTTIME; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.Lesson; +import seedu.address.model.student.NamePredicate; +import seedu.address.model.student.Student; +import seedu.address.model.student.exceptions.ConflictingLessonsException; + +/** + * Adds a lesson to a student. + */ +public class CreateLessonCommand extends Command { + + public static final String COMMAND_WORD = "new-lesson"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a lesson to students.\n" + + "Parameters: " + + PREFIX_NAME + "STUDENT_NAME " + + PREFIX_LESSON + "LESSON_NAME " + + PREFIX_STARTTIME + "Start time " + + PREFIX_ENDTIME + "End time\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_LESSON + "Math Lesson " + + PREFIX_STARTTIME + "2023-05-21 12:00 " + + PREFIX_ENDTIME + "2023-05-21 14:00"; + + public static final String MESSAGE_DATE = "endTime must be after startTime, both in the format YYYY-MM-DD HH:mm"; + + private final String lessonName; + private final LocalDateTime startTime; + private final LocalDateTime endTime; + private final NamePredicate predicate; + private final List names; + + /** + * Creates a CreateLessonCommand to add the specified {@code Lesson} + */ + public CreateLessonCommand(List names, NamePredicate predicate, String lessonName, LocalDateTime startTime, + LocalDateTime endTime) { + requireNonNull(lessonName); + requireNonNull(startTime); + requireNonNull(endTime); + requireNonNull(predicate); + + this.lessonName = lessonName; + this.startTime = startTime; + this.endTime = endTime; + this.predicate = predicate; + this.names = names; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + + StringBuilder nonExistNames = new StringBuilder(); + for (String name : names) { + if (model.noSuchStudent(name)) { + nonExistNames.append(name).append(", "); + } + } + if (nonExistNames.length() != 0) { + nonExistNames = new StringBuilder(nonExistNames.substring(0, nonExistNames.length() - 2)); + throw new CommandException(String.format(Messages.MESSAGE_NO_SUCH_STUDENT, nonExistNames)); + } + StringBuilder dupNames = new StringBuilder(); + for (String name : names) { + if (model.hasDuplicateName(name)) { + dupNames.append(name).append(", "); + } + } + if (dupNames.length() != 0) { + dupNames = new StringBuilder(dupNames.substring(0, dupNames.length() - 2)); + throw new CommandException(String.format(Messages.MESSAGE_HAS_DUPLICATE_NAMES, dupNames)); + } + + Lesson lesson = new Lesson(lessonName, startTime, endTime); + + if (model.hasConflictingLessonTime(lesson)) { + throw new CommandException(Messages.MESSAGE_CONFLICTING_LESSON_TIME); + } + + model.updateFilteredStudentList(predicate); + + List studentList = model.getFilteredStudentList(); + + if (startTime.isBefore(LocalDateTime.now())) { + throw new CommandException("start time must be in the future."); + } + + if (startTime.isAfter(endTime)) { + throw new CommandException(Messages.MESSAGE_INVALID_EXAM_TIME); + } + + if (Duration.between(startTime, endTime).toMinutes() < 30 || Duration.between(startTime, + endTime).toHours() > 3) { + throw new CommandException(Messages.MESSAGE_INVALID_LESSON_DURATION); + } + + try { + for (Student student : studentList) { + student.addLesson(lesson); + } + } catch (ConflictingLessonsException e) { + throw new CommandException(e.getMessage()); + } + + StringBuilder sb = new StringBuilder(); + for (Student student : studentList) { + sb.append(student.getName().fullName); + sb.append("\n"); + } + + return new CommandResult( + String.format(Messages.MESSAGE_LESSON_ADDED_SUCCESS, lesson, sb)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CreateLessonCommand // instanceof handles nulls + && predicate.equals(((CreateLessonCommand) other).predicate) + && lessonName.equals(((CreateLessonCommand) other).lessonName) + && startTime.equals(((CreateLessonCommand) other).startTime) + && endTime.equals(((CreateLessonCommand) other).endTime)); + } +} + diff --git a/src/main/java/seedu/address/logic/commands/lesson/DeleteLessonCommand.java b/src/main/java/seedu/address/logic/commands/lesson/DeleteLessonCommand.java new file mode 100644 index 00000000000..d2988919203 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/lesson/DeleteLessonCommand.java @@ -0,0 +1,110 @@ +package seedu.address.logic.commands.lesson; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; + +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.NamePredicate; +import seedu.address.model.student.Student; + +/** + * Deletes a lesson from a student. + */ +public class DeleteLessonCommand extends Command { + + public static final String COMMAND_WORD = "delete-lesson"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes a lesson from a student.\n" + + "Parameters: " + + PREFIX_NAME + "STUDENT_NAME " + + PREFIX_INDEX + "INDEX\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_INDEX + "1"; + + private final NamePredicate predicate; + private final Index targetIndex; + private final List names; + + + /** + * Creates a DeleteLessonCommand to delete the specified lesson from the specified student. + */ + public DeleteLessonCommand(List inputNames, NamePredicate predicate, Index targetIndex) { + requireNonNull(predicate); + requireNonNull(targetIndex); + + this.names = inputNames; + this.predicate = predicate; + this.targetIndex = targetIndex; + + } + + /** + * Executes the command and returns the result message. + * + * @param model {@code Model} which the command should operate on. + * @return feedback message of the operation result for display + * @throws CommandException if the command's preconditions are not met + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + + StringBuilder nonExistNames = new StringBuilder(); + for (String name : names) { + if (model.noSuchStudent(name)) { + nonExistNames.append(name).append(", "); + } + } + if (nonExistNames.length() != 0) { + nonExistNames = new StringBuilder(nonExistNames.substring(0, nonExistNames.length() - 2)); + throw new CommandException(String.format(Messages.MESSAGE_NO_SUCH_STUDENT, nonExistNames)); + } + StringBuilder dupNames = new StringBuilder(); + for (String name : names) { + if (model.hasDuplicateName(name)) { + dupNames.append(name).append(", "); + } + } + if (dupNames.length() != 0) { + dupNames = new StringBuilder(dupNames.substring(0, dupNames.length() - 2)); + throw new CommandException(String.format(Messages.MESSAGE_HAS_DUPLICATE_NAMES, dupNames)); + } + model.updateFilteredStudentList(predicate); + + List studentList = model.getFilteredStudentList(); + + StringBuilder sb = new StringBuilder(); + for (Student student : studentList) { + try { + sb.append(String.format(Messages.MESSAGE_LESSON_DELETED_SUCCESS, targetIndex.getOneBased(), + student.getLesson(targetIndex).toString(), student.getName().toString())); + sb.append("\n"); + student.deleteLesson(targetIndex); + } catch (IndexOutOfBoundsException e) { + throw new CommandException(Messages.MESSAGE_INVALID_LESSON_DISPLAYED_INDEX); + } + } + + return new CommandResult(sb.toString()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteLessonCommand // instanceof handles nulls + && predicate.equals(((DeleteLessonCommand) other).predicate)); + } +} + diff --git a/src/main/java/seedu/address/logic/commands/lesson/UpdateLessonCommand.java b/src/main/java/seedu/address/logic/commands/lesson/UpdateLessonCommand.java new file mode 100644 index 00000000000..f210605fe26 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/lesson/UpdateLessonCommand.java @@ -0,0 +1,151 @@ +package seedu.address.logic.commands.lesson; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ENDTIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LESSON; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STARTTIME; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.Lesson; +import seedu.address.model.student.NamePredicate; +import seedu.address.model.student.Student; + +/** + * Updates the information of an existing lesson. + */ +public class UpdateLessonCommand extends Command { + public static final String COMMAND_WORD = "update-lesson"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Update the information of an existing lesson.\n" + + "Parameters: " + + PREFIX_NAME + "STUDENT_NAME " + + PREFIX_INDEX + "LESSON_INDEX " + + PREFIX_LESSON + "HOMEWORK_NAME " + + PREFIX_STARTTIME + "START TIME" + + PREFIX_ENDTIME + "END TIME\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_INDEX + "1 " + + PREFIX_LESSON + "Math Lesson "; + + private final Index index; + private final Optional lessonName; + private final Optional startTime; + private final Optional endTime; + private final NamePredicate predicate; + private final List names; + + /** + * Creates a UpdateLessonCommand to update the specified {@code Lesson} + */ + public UpdateLessonCommand(List names, Index index, NamePredicate predicate, + Optional lessonName, Optional startTime, + Optional endTime) { + requireNonNull(predicate); + requireNonNull(index); + + this.index = index; + this.predicate = predicate; + this.lessonName = lessonName; + this.startTime = startTime; + this.endTime = endTime; + this.names = names; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + + StringBuilder nonExistNames = new StringBuilder(); + for (String name : names) { + if (model.noSuchStudent(name)) { + nonExistNames.append(name).append(", "); + } + } + if (nonExistNames.length() != 0) { + nonExistNames = new StringBuilder(nonExistNames.substring(0, nonExistNames.length() - 2)); + throw new CommandException(String.format(Messages.MESSAGE_NO_SUCH_STUDENT, nonExistNames)); + } + StringBuilder dupNames = new StringBuilder(); + for (String name : names) { + if (model.hasDuplicateName(name)) { + dupNames.append(name).append(", "); + } + } + if (dupNames.length() != 0) { + dupNames = new StringBuilder(dupNames.substring(0, dupNames.length() - 2)); + throw new CommandException(String.format(Messages.MESSAGE_HAS_DUPLICATE_NAMES, dupNames)); + } + model.updateFilteredStudentList(predicate); + List studentList = model.getFilteredStudentList(); + + Student student = studentList.get(0); + Lesson lessonToUpdate; + try { + lessonToUpdate = student.getLesson(index); + } catch (IndexOutOfBoundsException e) { + throw new CommandException(Messages.MESSAGE_INVALID_LESSON_DISPLAYED_INDEX); + } + + String newLessonName = this.lessonName.orElse(lessonToUpdate.getTitle()); + LocalDateTime newStartTime = this.startTime.orElse(lessonToUpdate.getStartTime()); + LocalDateTime newEndTime = this.endTime.orElse(lessonToUpdate.getEndTime()); + if (newStartTime.isAfter(newEndTime)) { + throw new CommandException(Messages.MESSAGE_INVALID_LESSON_TIME); + } + + if (Duration.between(newStartTime, newEndTime).toMinutes() < 30 || Duration.between(newStartTime, + newEndTime).toHours() > 3) { + throw new CommandException(Messages.MESSAGE_INVALID_LESSON_DURATION); + } + + Lesson newLesson = new Lesson(newLessonName, newStartTime, newEndTime); + model.updateFilteredStudentList(s -> s != student); + + if (model.hasConflictingLessonTime(newLesson)) { + throw new CommandException(Messages.MESSAGE_CONFLICTING_LESSON_TIME); + } + + if (model.hasConflictingExamTime(newLesson)) { + throw new CommandException(Messages.MESSAGE_CONFLICTING_EXAM_TIME); + } + + try { + student.setLesson(index.getZeroBased(), newLesson); + } catch (Exception e) { + throw new CommandException(e.getMessage()); + } + model.updateFilteredStudentList(s -> s == student); + return new CommandResult( + String.format(Messages.MESSAGE_LESSON_UPDATED_SUCCESS, index.getOneBased(), + student.getName().getFirstName(), + newLessonName, newStartTime, newEndTime)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UpdateLessonCommand // instanceof handles nulls + && predicate.equals(((UpdateLessonCommand) other).predicate) + && index.equals(((UpdateLessonCommand) other).index) + && lessonName.equals(((UpdateLessonCommand) other).lessonName) + && startTime.orElse(LocalDateTime.parse("2090-04-10T08:30:00")).isEqual((((UpdateLessonCommand) other)) + .startTime.orElse(LocalDateTime.parse("2090-04-10T08:30:00"))) + && endTime.orElse(LocalDateTime.parse("2090-04-10T08:30:00")).isEqual((((UpdateLessonCommand) other)) + .endTime.orElse(LocalDateTime.parse("2090-04-10T08:30:00")))); + } +} diff --git a/src/main/java/seedu/address/logic/commands/lesson/ViewLessonCommand.java b/src/main/java/seedu/address/logic/commands/lesson/ViewLessonCommand.java new file mode 100644 index 00000000000..c1cdd2411cf --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/lesson/ViewLessonCommand.java @@ -0,0 +1,171 @@ +package seedu.address.logic.commands.lesson; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LESSON; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.student.Lesson; +import seedu.address.model.student.Student; + +/** + * Finds and lists all lessons in address book whose name contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class ViewLessonCommand extends Command { + public static final String COMMAND_WORD = "view-lesson"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all lessons filtered by\n" + + "* name of student (case-insensitive) and/or\n" + + "* date and/or\n" + + "* lesson title and/or\n" + + "* whether it is done\n" + + "and displays them as a list with index numbers.\n" + + "Parameters: " + + PREFIX_NAME + "STUDENT_NAME " + + PREFIX_DATE + "DATE " + + PREFIX_LESSON + "LESSON " + + PREFIX_DONE + "COMPLETED KEYWORD\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_DATE + "2023-05-21 " + + PREFIX_LESSON + "Math " + + PREFIX_DONE + "done(or not done)"; + private static final String SEPERATOR = "--------------------------------------------------\n"; + private static final Predicate SHOW_ALL_LESSONS = lesson -> true; + + private final Predicate namePredicate; + private final Predicate lessonDatePredicate; + private final Predicate subjectPredicate; + private final Predicate donePredicate; + private final boolean defaultPredicateFlag; + private final List names; + + + /** + * Overloaded constructor for ViewLessonCommand. + * + * @param namePredicate Predicate to filter students by name. + * @param defaultPredicateFlag Flag to indicate if the default predicate is used. + */ + public ViewLessonCommand(List names, Predicate namePredicate, Predicate subjectPredicate, + Predicate donePredicate, boolean defaultPredicateFlag) { + this.names = names; + this.namePredicate = namePredicate; + this.lessonDatePredicate = SHOW_ALL_LESSONS; + this.defaultPredicateFlag = defaultPredicateFlag; + this.subjectPredicate = subjectPredicate; + this.donePredicate = donePredicate; + } + + /** + * Overloaded constructor for ViewLessonCommand. + * + * @param namePredicate Predicate to filter students by name. + * @param lessonDatePredicate Predicate to filter lessons by date. + * @param defaultPredicateFlag Flag to indicate if the default predicate is used. + */ + public ViewLessonCommand(List names, Predicate namePredicate, + Predicate lessonDatePredicate, Predicate subjectPredicate, + Predicate donePredicate, + boolean defaultPredicateFlag) { + this.names = names; + this.namePredicate = namePredicate; + this.lessonDatePredicate = lessonDatePredicate; + this.defaultPredicateFlag = defaultPredicateFlag; + this.subjectPredicate = subjectPredicate; + this.donePredicate = donePredicate; + } + + /** + * Executes the view-lesson command and returns the result message. + * + * @param model {@code Model} which the command should operate on. + * @return feedback message of the operation result for display + * @throws CommandException if an error occurs during command execution. + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + + StringBuilder nonExistNames = new StringBuilder(); + for (String name : names) { + if (model.noSuchStudent(name)) { + nonExistNames.append(name).append(", "); + } + } + if (nonExistNames.length() != 0) { + nonExistNames = new StringBuilder(nonExistNames.substring(0, nonExistNames.length() - 2)); + throw new CommandException(String.format(Messages.MESSAGE_NO_SUCH_STUDENT, nonExistNames)); + } + StringBuilder dupNames = new StringBuilder(); + for (String name : names) { + if (model.hasDuplicateName(name)) { + dupNames.append(name).append(", "); + } + } + if (dupNames.length() != 0) { + dupNames = new StringBuilder(dupNames.substring(0, dupNames.length() - 2)); + throw new CommandException(String.format(Messages.MESSAGE_HAS_DUPLICATE_NAMES, dupNames)); + } + model.updateFilteredStudentList(namePredicate); + + List studentList = model.getFilteredStudentList(); + + int numberOfStudents = studentList.size(); + int numOfLessons = 0; + StringBuilder sb = new StringBuilder(); + sb.append(SEPERATOR); + + // Loop through each student and add their lesson to the string builder + for (Student student : studentList) { + List lessonList = student.getFilteredLessonsList(lessonDatePredicate, subjectPredicate, + donePredicate); + if (!lessonList.isEmpty()) { + sb.append(student.getName().fullName).append(":\n"); + + numOfLessons += lessonList.size(); + + for (int i = 0; i < lessonList.size(); i++) { + sb.append(i + 1).append(". ").append(lessonList.get(i)).append("\n"); + } + + sb.append(SEPERATOR); + } + } + + // If no lessons are found, throw an exception + if (numOfLessons == 0) { + throw new CommandException(Messages.MESSAGE_NO_LESSON_FOUND); + } + + // If the default predicate is used, display a different message + if (defaultPredicateFlag) { + return new CommandResult( + String.format(Messages.MESSAGE_ALL_LESSONS_LISTED_OVERVIEW, numOfLessons, sb)); + } else { + return new CommandResult( + String.format(Messages.MESSAGE_LESSONS_LISTED_OVERVIEW, numOfLessons, numberOfStudents, sb)); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ViewLessonCommand // instanceof handles nulls + && namePredicate.equals(((ViewLessonCommand) other).namePredicate) + && lessonDatePredicate.equals(((ViewLessonCommand) other).lessonDatePredicate) + && defaultPredicateFlag == ((ViewLessonCommand) other).defaultPredicateFlag); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 3b8bfa035e8..33c2b86f429 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -3,20 +3,27 @@ import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GRADELEVEL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SCHOOL; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneAddress; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneEmail; +import static seedu.address.logic.parser.ParserUtil.checkMaxOnePhone; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNUllName; +import java.util.Optional; import java.util.Set; import java.util.stream.Stream; import seedu.address.logic.commands.AddCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; +import seedu.address.model.student.Address; +import seedu.address.model.student.Email; +import seedu.address.model.student.Name; +import seedu.address.model.student.Phone; +import seedu.address.model.student.Student; import seedu.address.model.tag.Tag; /** @@ -31,20 +38,40 @@ public class AddCommandParser implements Parser { */ public AddCommand parse(String args) throws ParseException { ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_ADDRESS, PREFIX_TAG, PREFIX_GRADELEVEL, + PREFIX_SCHOOL); if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) || !argMultimap.getPreamble().isEmpty()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); } + checkUniqueNotNUllName(argMultimap); + checkMaxOneAddress(argMultimap); + checkMaxOnePhone(argMultimap); + checkMaxOneEmail(argMultimap); + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - Person person = new Person(name, phone, email, address, tagList); + //Add school Tag if sc/ present + Optional schoolOptional = argMultimap.getValue(PREFIX_SCHOOL); + if (!schoolOptional.isEmpty()) { + Tag school = ParserUtil.parseTag(schoolOptional.get()); + tagList.add(school); + } + //Add grade level Tag if lv/ present + Optional gradeLevelOptional = argMultimap.getValue(PREFIX_GRADELEVEL); + if (!gradeLevelOptional.isEmpty()) { + Tag gradeLevel = ParserUtil.parseTag(gradeLevelOptional.get()); + tagList.add(gradeLevel); + } + + Student person = new Student(name, phone, email, address, tagList); return new AddCommand(person); } diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 1e466792b46..ac94af2d011 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -15,7 +15,36 @@ import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.ViewProfileCommand; +import seedu.address.logic.commands.exam.CreateExamCommand; +import seedu.address.logic.commands.exam.DeleteExamCommand; +import seedu.address.logic.commands.exam.UpdateExamCommand; +import seedu.address.logic.commands.exam.ViewExamCommand; +import seedu.address.logic.commands.homework.CreateHomeworkCommand; +import seedu.address.logic.commands.homework.DeleteHomeworkCommand; +import seedu.address.logic.commands.homework.MarkHomeworkAsDoneCommand; +import seedu.address.logic.commands.homework.MarkHomeworkAsUndoCommand; +import seedu.address.logic.commands.homework.UpdateHomeworkCommand; +import seedu.address.logic.commands.homework.ViewHomeworkCommand; +import seedu.address.logic.commands.lesson.CreateLessonCommand; +import seedu.address.logic.commands.lesson.DeleteLessonCommand; +import seedu.address.logic.commands.lesson.UpdateLessonCommand; +import seedu.address.logic.commands.lesson.ViewLessonCommand; +import seedu.address.logic.parser.exam.CreateExamCommandParser; +import seedu.address.logic.parser.exam.DeleteExamCommandParser; +import seedu.address.logic.parser.exam.UpdateExamCommandParser; +import seedu.address.logic.parser.exam.ViewExamCommandParser; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.logic.parser.homework.CreateHomeworkCommandParser; +import seedu.address.logic.parser.homework.DeleteHomeworkCommandParser; +import seedu.address.logic.parser.homework.MarkHomeworkAsDoneCommandParser; +import seedu.address.logic.parser.homework.MarkHomeworkAsUndoCommandParser; +import seedu.address.logic.parser.homework.UpdateHomeworkCommandParser; +import seedu.address.logic.parser.homework.ViewHomeworkCommandParser; +import seedu.address.logic.parser.lesson.CreateLessonCommandParser; +import seedu.address.logic.parser.lesson.DeleteLessonCommandParser; +import seedu.address.logic.parser.lesson.UpdateLessonCommandParser; +import seedu.address.logic.parser.lesson.ViewLessonCommandParser; /** * Parses user input. @@ -68,9 +97,56 @@ public Command parseCommand(String userInput) throws ParseException { case HelpCommand.COMMAND_WORD: return new HelpCommand(); + // Homework commands + case CreateHomeworkCommand.COMMAND_WORD: + return new CreateHomeworkCommandParser().parse(arguments); + + case ViewHomeworkCommand.COMMAND_WORD: + return new ViewHomeworkCommandParser().parse(arguments); + + case MarkHomeworkAsUndoCommand.COMMAND_WORD: + return new MarkHomeworkAsUndoCommandParser().parse(arguments); + + case DeleteHomeworkCommand.COMMAND_WORD: + return new DeleteHomeworkCommandParser().parse(arguments); + + case MarkHomeworkAsDoneCommand.COMMAND_WORD: + return new MarkHomeworkAsDoneCommandParser().parse(arguments); + + case UpdateHomeworkCommand.COMMAND_WORD: + return new UpdateHomeworkCommandParser().parse(arguments); + + // Lesson commands + case ViewLessonCommand.COMMAND_WORD: + return new ViewLessonCommandParser().parse(arguments); + + case CreateLessonCommand.COMMAND_WORD: + return new CreateLessonCommandParser().parse(arguments); + + case DeleteLessonCommand.COMMAND_WORD: + return new DeleteLessonCommandParser().parse(arguments); + + // Exam commands + case CreateExamCommand.COMMAND_WORD: + return new CreateExamCommandParser().parse(arguments); + + case ViewExamCommand.COMMAND_WORD: + return new ViewExamCommandParser().parse(arguments); + + case DeleteExamCommand.COMMAND_WORD: + return new DeleteExamCommandParser().parse(arguments); + + case UpdateLessonCommand.COMMAND_WORD: + return new UpdateLessonCommandParser().parse(arguments); + + case UpdateExamCommand.COMMAND_WORD: + return new UpdateExamCommandParser().parse(arguments); + + case ViewProfileCommand.COMMAND_WORD: + return new ViewProfileCommandParser().parse(arguments); + default: throw new ParseException(MESSAGE_UNKNOWN_COMMAND); } } - } diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf119..b33b0b1dd40 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -6,10 +6,25 @@ public class CliSyntax { /* Prefix definitions */ - public static final Prefix PREFIX_NAME = new Prefix("n/"); - public static final Prefix PREFIX_PHONE = new Prefix("p/"); - public static final Prefix PREFIX_EMAIL = new Prefix("e/"); - public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); - public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_NAME = new Prefix("name/"); + public static final Prefix PREFIX_PHONE = new Prefix("phone/"); + public static final Prefix PREFIX_EMAIL = new Prefix("email/"); + public static final Prefix PREFIX_ADDRESS = new Prefix("address/"); + public static final Prefix PREFIX_GRADELEVEL = new Prefix("level/"); + public static final Prefix PREFIX_SCHOOL = new Prefix("school/"); + public static final Prefix PREFIX_TAG = new Prefix("tag/"); + public static final Prefix PREFIX_HOMEWORK = new Prefix("homework/"); + public static final Prefix PREFIX_DEADLINE = new Prefix("deadline/"); + public static final Prefix PREFIX_EXAM = new Prefix("exam/"); + public static final Prefix PREFIX_WEIGHT = new Prefix("weightage/"); + public static final Prefix PREFIX_GRADE = new Prefix("grade/"); + public static final Prefix PREFIX_STATUS = new Prefix("status/"); + public static final Prefix PREFIX_INDEX = new Prefix("index/"); + + public static final Prefix PREFIX_LESSON = new Prefix("lesson/"); + public static final Prefix PREFIX_STARTTIME = new Prefix("start/"); + public static final Prefix PREFIX_ENDTIME = new Prefix("end/"); + public static final Prefix PREFIX_DATE = new Prefix("date/"); + public static final Prefix PREFIX_DONE = new Prefix("done/"); } diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java index 522b93081cc..e956f24df66 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java @@ -1,6 +1,11 @@ package seedu.address.logic.parser; +import static java.util.Objects.requireNonNull; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNullIndex; + +import java.util.stream.Stream; import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.DeleteCommand; @@ -17,13 +22,31 @@ public class DeleteCommandParser implements Parser { * @throws ParseException if the user input does not conform the expected format */ public DeleteCommand parse(String args) throws ParseException { - try { - Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); - } catch (ParseException pe) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); + requireNonNull(args); + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_INDEX); + + if (!arePrefixesPresent(argMultimap, PREFIX_INDEX) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteCommand.MESSAGE_USAGE)); } + + checkUniqueNotNullIndex(argMultimap); + + Index index = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INDEX).get()); + return new DeleteCommand(index); + } + + /** + * Returns true if all prefixes are present in the given {@code ArgumentMultimap}. + * + * @param argumentMultimap the argument multimap to check for prefixes. + * @param prefixes the prefixes to be checked. + * @return true if all prefixes are present in the given {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); } } diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index 845644b7dea..dc59d698037 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -4,18 +4,25 @@ import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneAddress; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneEmail; +import static seedu.address.logic.parser.ParserUtil.checkMaxOnePhone; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNUllName; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNullIndex; import java.util.Collection; import java.util.Collections; import java.util.Optional; import java.util.Set; +import java.util.stream.Stream; import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; +import seedu.address.logic.commands.EditCommand.EditStudentDescriptor; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.tag.Tag; @@ -32,36 +39,43 @@ public class EditCommandParser implements Parser { public EditCommand parse(String args) throws ParseException { requireNonNull(args); ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_INDEX, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + PREFIX_TAG); - Index index; - - try { - index = ParserUtil.parseIndex(argMultimap.getPreamble()); - } catch (ParseException pe) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); + if (!arePrefixesPresent(argMultimap, PREFIX_INDEX) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + EditCommand.MESSAGE_USAGE)); } - EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); + checkMaxOneAddress(argMultimap); + checkMaxOneEmail(argMultimap); + checkMaxOnePhone(argMultimap); + checkUniqueNotNullIndex(argMultimap); + + Index index = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INDEX).get()); + + EditCommand.EditStudentDescriptor editStudentDescriptor = new EditStudentDescriptor(); if (argMultimap.getValue(PREFIX_NAME).isPresent()) { - editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); + checkUniqueNotNUllName(argMultimap); + editStudentDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); } if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { - editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); + editStudentDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); } if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { - editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); + editStudentDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); } if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { - editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); + editStudentDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); + parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editStudentDescriptor::setTags); - if (!editPersonDescriptor.isAnyFieldEdited()) { + if (!editStudentDescriptor.isAnyFieldEdited()) { throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); } - return new EditCommand(index, editPersonDescriptor); + return new EditCommand(index, editStudentDescriptor); } /** @@ -79,4 +93,15 @@ private Optional> parseTagsForEdit(Collection tags) throws Pars return Optional.of(ParserUtil.parseTags(tagSet)); } + /** + * Returns true if all prefixes are present in the given {@code ArgumentMultimap}. + * + * @param argumentMultimap the argument multimap to check for prefixes. + * @param prefixes the prefixes to be checked. + * @return true if all prefixes are present in the given {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + } diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java index 4fb71f23103..7396eb95878 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -1,12 +1,18 @@ package seedu.address.logic.parser; +import static java.util.Objects.requireNonNull; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Stream; import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.exam.ViewExamCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.student.NamePredicate; +import seedu.address.model.student.Student; /** * Parses input arguments and creates a new FindCommand object @@ -19,15 +25,38 @@ public class FindCommandParser implements Parser { * @throws ParseException if the user input does not conform the expected format */ public FindCommand parse(String args) throws ParseException { - String trimmedArgs = args.trim(); - if (trimmedArgs.isEmpty()) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + requireNonNull(args); + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME) || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); } - String[] nameKeywords = trimmedArgs.split("\\s+"); + Predicate namePredicate; + List nameKeywords = argMultimap.getAllValues(PREFIX_NAME); + // for all the names, trim the name and only take the first word + for (int i = 0; i < nameKeywords.size(); i++) { + String name = nameKeywords.get(i); + name = name.trim(); + if (name.trim().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ViewExamCommand.MESSAGE_USAGE)); + } + nameKeywords.set(i, name); + } + return new FindCommand(new NamePredicate(nameKeywords)); + } - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + /** + * Returns true if all prefixes are present in the given {@code ArgumentMultimap}. + * + * @param argumentMultimap the argument multimap to check for prefixes. + * @param prefixes the prefixes to be checked. + * @return true if all prefixes are present in the given {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); } } diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..d301713498f 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -1,27 +1,93 @@ package seedu.address.logic.parser; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ENDTIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GRADE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_HOMEWORK; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LESSON; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STARTTIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STATUS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_WEIGHT; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Set; +import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; import seedu.address.commons.util.StringUtil; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; +import seedu.address.model.student.Address; +import seedu.address.model.student.Email; +import seedu.address.model.student.Grade; +import seedu.address.model.student.Homework; +import seedu.address.model.student.Name; +import seedu.address.model.student.Phone; import seedu.address.model.tag.Tag; /** * Contains utility methods used for parsing strings in the various *Parser classes. */ public class ParserUtil { - public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; + //@@author Yufannn-reused + //Reused from https://github.com/RussellDash332/ip/blob/master/src/main/java/stashy/parser/Parser.java + //with minor modification, it is a pretty good way to organise and extend the acceptable date format. + private static final String[] ACCEPTABLE_DATETIME_FORMATS = { + "MMM dd yyyy HHmm", "MMM dd yyyy HH:mm", + "yyyy-MM-dd'T'HH:mm", "dd/MM/yyyy HHmm", + "dd/MM/yyyy HH:mm", "yyyy/MM/dd HHmm", + "yyyy/MM/dd HH:mm", "yyyy/MM/dd'T'HHmm", + "yyyy/MM/dd'T'HH:mm", "yyyy-MM-dd HHmm", + "yyyy-MM-dd HH:mm", "dd MMM yyyy HHmm", + "dd MMM yyyy HH:mm", "MMM dd, yyyy HHmm", + "MMM dd, yyyy HH:mm" + }; + //@@author + + //@@author NBQian-reused + //Reused from https://github.com/Yufannnn/ip/blob/master/src/main/java/duke/parser/TimeHandler.java + //with minor modification, it is a pretty good way to organise and extend the acceptable date format. + private static final String[] ACCEPTABLE_DATE_FORMATS = { + "MMM dd yyyy", "yyyy-MM-dd", "dd/MM/yyyy", "yyyy/MM/dd", + "dd MMM yyyy", "MMM dd, yyyy", "dd-mm-yyyy" + }; + //@@author + + /** + * Parses a string to a LocalDateTime object using the acceptable date time formats defined + * + * @param date The date string to be parsed + * @return The parsed LocalDate object + * @throws ParseException if the string does not match any supported date formats + */ + public static LocalDate parseDate(String date) throws ParseException { + for (String dateFormat : ACCEPTABLE_DATE_FORMATS) { + try { + return LocalDate.parse(date, DateTimeFormatter.ofPattern(dateFormat)); + } catch (Exception e) { + // Go to the next dateFormat + } + } + throw new ParseException("Invalid date format. Please use one of the following formats:\n" + + String.join("\n", ACCEPTABLE_DATE_FORMATS)); + } + /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be * trimmed. @@ -107,6 +173,9 @@ public static Tag parseTag(String tag) throws ParseException { if (!Tag.isValidTagName(trimmedTag)) { throw new ParseException(Tag.MESSAGE_CONSTRAINTS); } + if (!Tag.isValidTagLength(trimmedTag)) { + throw new ParseException(Tag.MESSAGE_TAG_LENGTH); + } return new Tag(trimmedTag); } @@ -121,4 +190,389 @@ public static Set parseTags(Collection tags) throws ParseException } return tagSet; } + + //@@author Yufannnn-reused + //Reused from https://github.com/wweqg/ip/blob/master/src/main/java/duke/parser/Parser.java + //with minor modification, it is a pretty clean and concise regular expression for general instructions + /** + * Parses a string to a LocalDateTime object using the acceptable date time formats defined + * in {@link #ACCEPTABLE_DATETIME_FORMATS}. + * + * @param date The date string to be parsed + * @return The parsed LocalDateTime object + * @throws ParseException if the date string does not match any of the acceptable date time formats + */ + public static LocalDateTime parseDeadline(String date) throws ParseException { + for (String dateTimeFormat : ACCEPTABLE_DATETIME_FORMATS) { + try { + return LocalDateTime.parse(date, + DateTimeFormatter.ofPattern(dateTimeFormat)); + } catch (Exception e) { + // Go to the next dateTimeFormat + } + } + + throw new ParseException("Invalid date format. Please use one of the following formats:\n" + + String.join("\n", ACCEPTABLE_DATETIME_FORMATS)); + } + + /** + * Parses a string to a LocalDateTime object using the acceptable date time formats defined + * in {@link #ACCEPTABLE_DATETIME_FORMATS}. + * + * @param date The date string to be parsed + * @return The parsed LocalDateTime object + * @throws ParseException if the date string does not match any of the acceptable date time formats + */ + public static LocalDateTime parseStartTime(String date) throws ParseException { + for (String dateTimeFormat : ACCEPTABLE_DATETIME_FORMATS) { + try { + return LocalDateTime.parse(date, + DateTimeFormatter.ofPattern(dateTimeFormat)); + } catch (Exception e) { + // Go to the next dateTimeFormat + } + } + + throw new ParseException("Invalid date format. Please use one of the following formats:\n" + + String.join("\n", ACCEPTABLE_DATETIME_FORMATS)); + } + /** + * Parses a string to a LocalDateTime object using the acceptable date time formats defined + * in {@link #ACCEPTABLE_DATETIME_FORMATS}. + * + * @param date The date string to be parsed + * @return The parsed LocalDateTime object + * @throws ParseException if the date string does not match any of the acceptable date time formats + */ + public static LocalDateTime parseEndTime(String date) throws ParseException { + for (String dateTimeFormat : ACCEPTABLE_DATETIME_FORMATS) { + try { + return LocalDateTime.parse(date, + DateTimeFormatter.ofPattern(dateTimeFormat)); + } catch (Exception e) { + // Go to the next dateTimeFormat + } + } + + throw new ParseException("Invalid date format. Please use one of the following formats:\n" + + String.join("\n", ACCEPTABLE_DATETIME_FORMATS)); + } + + /** + * Parses a string to a LocalDateTime object using the acceptable date time formats defined + * + * @param status The status string to be parsed + * @return The parsed Boolean object + * @throws ParseException if the status string does not match any of the acceptable status + */ + public static Boolean parseStatus(String status) throws ParseException { + if (status.equalsIgnoreCase(Homework.Status.COMPLETED.toString())) { + return true; + } else if (status.equalsIgnoreCase(Homework.Status.PENDING.toString())) { + return false; + } else { + throw new ParseException("Invalid status. Please use either 'completed' or 'pending'."); + } + } + + /** + * Checks if the deadline is unique and not null. + * + * @param argMultimap the argument multimap to check for deadline. + * @throws ParseException if the deadline is not unique or null. + */ + public static void checkUniqueNotNUllDeadline(ArgumentMultimap argMultimap) throws ParseException { + // only one deadline keyword is allowed + if (argMultimap.getAllValues(PREFIX_DEADLINE).size() > 1) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + Messages.MESSAGE_ONLY_ONE_DEADLINE)); + } + // it cannot be empty + if (argMultimap.getValue(PREFIX_DEADLINE).isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + Messages.MESSAGE_EMPTY_DEADLINE)); + } + } + + /** + * Checks if the name is unique and not null. + * + * @param argMultimap the argument multimap to check for name. + * @throws ParseException if the name is not unique or null. + */ + public static void checkUniqueNotNUllName(ArgumentMultimap argMultimap) throws ParseException { + // only one name keyword is allowed + if (argMultimap.getAllValues(PREFIX_NAME).size() > 1) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + Messages.MESSAGE_ONLY_ONE_STUDENT)); + } + // it cannot be empty + if (argMultimap.getValue(PREFIX_NAME).isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + Messages.MESSAGE_EMPTY_STUDENT)); + } + } + + /** + * Checks if the homework name is unique and not null. + * + * @param argMultimap the argument multimap to check for homework name. + * @throws ParseException if the homework name is not unique or null. + */ + public static void checkUniqueNotNUllHomework(ArgumentMultimap argMultimap) throws ParseException { + // only one homework keyword is allowed + if (argMultimap.getAllValues(PREFIX_HOMEWORK).size() > 1) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + Messages.MESSAGE_ONLY_ONE_HOMEWORK)); + } + // it cannot be empty + if (argMultimap.getValue(PREFIX_HOMEWORK).isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + Messages.MESSAGE_EMPTY_HOMEWORK)); + } + } + + /** + * Checks if the index is unique and not null. + * + * @param argMultimap the argument multimap to check for index. + * @throws ParseException if the index is not unique or null. + */ + public static void checkUniqueNotNullIndex(ArgumentMultimap argMultimap) throws ParseException { + // only one index keyword is allowed + if (argMultimap.getAllValues(PREFIX_INDEX).size() > 1) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + Messages.MESSAGE_ONLY_ONE_INDEX)); + } + // it cannot be empty + if (argMultimap.getValue(PREFIX_INDEX).isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + Messages.MESSAGE_EMPTY_INDEX)); + } + } + /** + * Checks if at most one lesson prefix is present. + * @param argumentMultimap the argument multimap to check for lesson. + * @throws ParseException if more than one lesson prefix is present. + */ + public static void checkMaxOneLesson(ArgumentMultimap argumentMultimap) throws ParseException { + if (argumentMultimap.getAllValues(PREFIX_LESSON).size() > 1) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + Messages.MESSAGE_ONLY_ONE_LESSON)); + } + } + /** + * Checks if at most one student name prefix is present. + * @param argumentMultimap the argument multimap to check for name. + * @throws ParseException if more than one name prefix is present. + */ + public static void checkMaxOneEmail(ArgumentMultimap argumentMultimap) throws ParseException { + if (argumentMultimap.getAllValues(PREFIX_EMAIL).size() > 1) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + Messages.MESSAGE_ONLY_ONE_EMAIL)); + } + } + /** + * Checks if at most one start time prefix is present. + * @param argumentMultimap the argument multimap to check for start time. + * @throws ParseException if more than one start time prefix is present. + */ + public static void checkMaxOneStartTime(ArgumentMultimap argumentMultimap) throws ParseException { + if (argumentMultimap.getAllValues(PREFIX_STARTTIME).size() > 1) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + Messages.MESSAGE_ONLY_ONE_STARTTIME)); + } + } + /** + * Checks if at most one end time prefix is present. + * @param argumentMultimap the argument multimap to check for end time. + * @throws ParseException if more than one end time prefix is present. + */ + public static void checkMaxOneEndTime(ArgumentMultimap argumentMultimap) throws ParseException { + if (argumentMultimap.getAllValues(PREFIX_ENDTIME).size() > 1) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + Messages.MESSAGE_ONLY_ONE_ENDTIME)); + } + } + /** + * Checks if at most one address prefix is present. + * @param argumentMultimap the argument multimap to check for address. + * @throws ParseException if more than one address prefix is present. + */ + public static void checkMaxOneAddress(ArgumentMultimap argumentMultimap) throws ParseException { + if (argumentMultimap.getAllValues(PREFIX_ADDRESS).size() > 1) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + Messages.MESSAGE_ONLY_ONE_ADDRESS)); + } + } + /** + * Checks if at most one phone prefix is present. + * @param argumentMultimap the argument multimap to check for phone. + * @throws ParseException if more than one phone prefix is present. + */ + public static void checkMaxOnePhone(ArgumentMultimap argumentMultimap) throws ParseException { + if (argumentMultimap.getAllValues(PREFIX_PHONE).size() > 1) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + Messages.MESSAGE_ONLY_ONE_PHONE)); + } + } + /** + * Checks if at most one done prefix is present. + * @param argumentMultimap the argument multimap to check for done. + * @throws ParseException if more than one done prefix is present. + */ + public static void checkMaxOneDone(ArgumentMultimap argumentMultimap) throws ParseException { + if (argumentMultimap.getAllValues(PREFIX_DONE).size() > 1) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + Messages.MESSAGE_ONLY_ONE_DONE)); + } + } + /** + * Checks if at most one date prefix is present. + * @param argumentMultimap the argument multimap to check for date. + * @throws ParseException if more than one date prefix is present. + */ + public static void checkMaxOneDate(ArgumentMultimap argumentMultimap) throws ParseException { + if (argumentMultimap.getAllValues(PREFIX_DATE).size() > 1) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + Messages.MESSAGE_ONLY_ONE_DATE)); + } + } + /** + * Checks if at most one grade prefix is present. + * @param argumentMultimap the argument multimap to check for grade. + * @throws ParseException if more than one grade prefix is present. + */ + public static void checkMaxOneGrade(ArgumentMultimap argumentMultimap) throws ParseException { + if (argumentMultimap.getAllValues(PREFIX_GRADE).size() > 1) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + Messages.MESSAGE_ONLY_ONE_GRADE)); + } + } + /** + * Checks if at most one weight prefix is present. + * @param argumentMultimap the argument multimap to check for weight. + * @throws ParseException if more than one weight prefix is present. + */ + public static void checkMaxOneWeight(ArgumentMultimap argumentMultimap) throws ParseException { + if (argumentMultimap.getAllValues(PREFIX_WEIGHT).size() > 1) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + Messages.MESSAGE_ONLY_ONE_WEIGHTAGE)); + } + } + /** + * Checks if at most one exam prefix is present. + * @param argumentMultimap the argument multimap to check for exam. + * @throws ParseException if more than one exam prefix is present. + */ + public static void checkMaxOneExam(ArgumentMultimap argumentMultimap) throws ParseException { + if (argumentMultimap.getAllValues(PREFIX_EXAM).size() > 1) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + Messages.MESSAGE_ONLY_ONE_EXAM)); + } + } + + + /** + * Checks if the index is unique and not null. + * + * @param argMultimap the argument multimap to check for index. + * @throws ParseException if the index is not unique or null. + */ + public static void checkUniqueNotNullStatus(ArgumentMultimap argMultimap) throws ParseException { + // only one index keyword is allowed + if (argMultimap.getAllValues(PREFIX_STATUS).size() > 1) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + Messages.MESSAGE_ONLY_ONE_STATUS)); + } + // it cannot be empty + if (argMultimap.getValue(PREFIX_STATUS).isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + Messages.MESSAGE_EMPTY_STATUS)); + } + } + + /** + * Checks if the index is unique and not null. + * + * @param argMultimap the argument multimap to check for index. + * @throws ParseException if the index is not unique or null. + */ + public static void checkUniqueHomework(ArgumentMultimap argMultimap) throws ParseException { + // only one homework keyword is allowed + if (argMultimap.getAllValues(PREFIX_HOMEWORK).size() > 1) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + Messages.MESSAGE_ONLY_ONE_HOMEWORK)); + } + } + + /** + * Checks if the homework name is not null. + * + * @param homework the homework name to check for null. + * @throws ParseException if the homework name is null. + */ + public static void checkNotNullHomework(String homework) throws ParseException { + // it cannot be empty + if (homework.isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + Messages.MESSAGE_EMPTY_HOMEWORK)); + } + } + + /** + * Checks if the name is not null. + * + * @param names the name to check for null. + * @throws ParseException if the name is null. + */ + public static void checkNotNullNames(List names) throws ParseException { + for (String name : names) { + if (name.isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + Messages.MESSAGE_EMPTY_STUDENT)); + } + } + } + + /** + * parses a string and returns a double representing the percentage weightage + * @param weight string to parse + * @return Double percentage + * @throws ParseException + */ + public static double parseWeightage(String weight) throws ParseException { + if (!weight.matches("^[0-9]+(?:\\.[0-9]+)?%?$")) { + throw new ParseException("Weightage is in an invalid format!"); + } + weight = weight.replace("%", ""); //removes % sign if it exists + Double res = null; + try { + res = Double.parseDouble(weight); + } catch (NumberFormatException e) { + throw new ParseException("unexpected error occurred when parsing weightage", e); + } + return res; + } + + /** + * parses a string and returns a Grade object representing it + * @param grade string to parse + * @return Grade representationn + * @throws ParseException + */ + public static Grade parseGrade(String grade) throws ParseException { + if (!grade.matches("^[0-9]+/[0-9]+$")) { + throw new ParseException("Grade is in an invalid format!"); + } + Grade res; + try { + res = new Grade(Double.parseDouble(grade.split("/")[0]), Double.parseDouble(grade.split("/")[1])); + } catch (IllegalArgumentException e) { + throw new ParseException(e.getMessage()); + } + return res; + } + } diff --git a/src/main/java/seedu/address/logic/parser/ViewProfileCommandParser.java b/src/main/java/seedu/address/logic/parser/ViewProfileCommandParser.java new file mode 100644 index 00000000000..808cef9257d --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ViewProfileCommandParser.java @@ -0,0 +1,61 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.ViewProfileCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.student.NamePredicate; +import seedu.address.model.student.Student; + + + +/** + * Parses input arguments and creates a new ViewProfileCommand object + */ +public class ViewProfileCommandParser implements Parser { + @Override + public Command parse(String args) throws ParseException { + requireNonNull(args); + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME); + + if (!argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ViewProfileCommand.MESSAGE_USAGE)); + } + + Predicate namePredicate; + List nameList = new ArrayList<>(); + + // If name is present, create a predicate to filter by name + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + + List nameKeywords = argMultimap.getAllValues(PREFIX_NAME); + // for all the names, trim the name and only take the first word + for (int i = 0; i < nameKeywords.size(); i++) { + String name = nameKeywords.get(i); + name = name.trim(); + if (name.trim().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ViewProfileCommand.MESSAGE_USAGE)); + } + nameKeywords.set(i, name); + } + nameList = nameKeywords; + namePredicate = new NamePredicate(nameKeywords); + } else { + namePredicate = PREDICATE_SHOW_ALL_STUDENTS; + } + + + return new ViewProfileCommand(nameList, namePredicate); + } +} diff --git a/src/main/java/seedu/address/logic/parser/exam/CreateExamCommandParser.java b/src/main/java/seedu/address/logic/parser/exam/CreateExamCommandParser.java new file mode 100644 index 00000000000..e7559562a9a --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/exam/CreateExamCommandParser.java @@ -0,0 +1,104 @@ +package seedu.address.logic.parser.exam; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ENDTIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GRADE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STARTTIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_WEIGHT; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneEndTime; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneExam; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneGrade; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneStartTime; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneWeight; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import seedu.address.logic.commands.exam.CreateExamCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.student.Grade; +import seedu.address.model.student.NamePredicate; + +/** + * Parses input arguments and creates a new CreateHomeworkExam object + */ +public class CreateExamCommandParser implements Parser { + private List names = new ArrayList<>(); + /** + * Parses the given {@code String} of arguments in the context of the CreateExamCommand + * and returns a CreateExamCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public CreateExamCommand parse(String args) throws ParseException { + requireNonNull(args); + + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_EXAM, PREFIX_STARTTIME, PREFIX_ENDTIME, + PREFIX_WEIGHT, PREFIX_GRADE); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_EXAM, PREFIX_STARTTIME, PREFIX_ENDTIME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CreateExamCommand.MESSAGE_USAGE)); + } + + checkMaxOneExam(argMultimap); + checkMaxOneStartTime(argMultimap); + checkMaxOneEndTime(argMultimap); + checkMaxOneWeight(argMultimap); + checkMaxOneGrade(argMultimap); + + String examDescription = argMultimap.getValue(PREFIX_EXAM).get(); + if (examDescription.trim().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CreateExamCommand.MESSAGE_USAGE)); + } + LocalDateTime startTime = ParserUtil.parseStartTime(argMultimap.getValue(PREFIX_STARTTIME).get()); + LocalDateTime endTime = ParserUtil.parseEndTime(argMultimap.getValue(PREFIX_ENDTIME).get()); + List nameKeywords = argMultimap.getAllValues(PREFIX_NAME); + + Double weightage = null; + //if the weightage of an exam is provided, store the weight + if (argMultimap.getValue(PREFIX_WEIGHT).isPresent()) { + weightage = ParserUtil.parseWeightage(argMultimap.getValue(PREFIX_WEIGHT).get()); + } + + Grade grade = null; + //if the grade of an exam is provided, store the grade + if (argMultimap.getValue(PREFIX_GRADE).isPresent()) { + grade = ParserUtil.parseGrade(argMultimap.getValue(PREFIX_GRADE).get()); + } + + // for all the names, trim the name and only take the first word + for (int i = 0; i < nameKeywords.size(); i++) { + String name = nameKeywords.get(i); + name = name.trim(); + if (name.trim().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CreateExamCommand.MESSAGE_USAGE)); + } + nameKeywords.set(i, name); + } + names = nameKeywords; + + return new CreateExamCommand(names, new NamePredicate(nameKeywords), + examDescription, startTime, endTime, weightage, grade); + } + + /** + * Returns true if all prefixes are present in the given {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/exam/DeleteExamCommandParser.java b/src/main/java/seedu/address/logic/parser/exam/DeleteExamCommandParser.java new file mode 100644 index 00000000000..138aa51b27d --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/exam/DeleteExamCommandParser.java @@ -0,0 +1,73 @@ +package seedu.address.logic.parser.exam; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNUllName; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNullIndex; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exam.DeleteExamCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.student.NamePredicate; + +/** + * Parses input arguments and creates a new DeleteExamCommand object + */ +public class DeleteExamCommandParser implements Parser { + private List inputNames = new ArrayList<>(); + /** + * Parses the given {@code String} of arguments in the context of the DeleteExamCommand + * and returns an DeleteExamCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteExamCommand parse(String args) throws ParseException { + requireNonNull(args); + + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_INDEX); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_INDEX) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteExamCommand.MESSAGE_USAGE)); + } + + checkUniqueNotNullIndex(argMultimap); + checkUniqueNotNUllName(argMultimap); + + List nameKeywords = argMultimap.getAllValues(PREFIX_NAME); + // for all the names, trim the name and only take the first word + for (int i = 0; i < nameKeywords.size(); i++) { + String name = nameKeywords.get(i); + name = name.trim(); + if (name.trim().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteExamCommand.MESSAGE_USAGE)); + } + nameKeywords.set(i, name); + } + inputNames = nameKeywords; + + Index index = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INDEX).get()); + return new DeleteExamCommand(inputNames, new NamePredicate(nameKeywords), index); + } + + /** + * Returns true if all prefixes are present in the given {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/exam/UpdateExamCommandParser.java b/src/main/java/seedu/address/logic/parser/exam/UpdateExamCommandParser.java new file mode 100644 index 00000000000..db85b5089d9 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/exam/UpdateExamCommandParser.java @@ -0,0 +1,134 @@ +package seedu.address.logic.parser.exam; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ENDTIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GRADE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STARTTIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_WEIGHT; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneEndTime; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneExam; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneGrade; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneStartTime; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneWeight; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNUllName; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNullIndex; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exam.UpdateExamCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.student.Grade; +import seedu.address.model.student.NamePredicate; + +/** + * Parses input arguments and creates a new UpdateExamCommand object + */ +public class UpdateExamCommandParser implements Parser { + private List names = new ArrayList<>(); + /** + * Parses the given {@code String} of arguments in the context of the UpdateExamCommand + * and returns an UpdateExamCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public UpdateExamCommand parse(String args) throws ParseException { + requireNonNull(args); + + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_INDEX, PREFIX_EXAM, PREFIX_STARTTIME, + PREFIX_ENDTIME, PREFIX_WEIGHT, PREFIX_GRADE); + + if ((!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_INDEX, PREFIX_EXAM) + && !arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_INDEX, PREFIX_STARTTIME) + && !arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_INDEX, PREFIX_ENDTIME) + && !arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_INDEX, PREFIX_WEIGHT) + && !arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_INDEX, PREFIX_GRADE)) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + UpdateExamCommand.MESSAGE_USAGE)); + } + + checkMaxOneExam(argMultimap); + checkMaxOneStartTime(argMultimap); + checkMaxOneEndTime(argMultimap); + checkMaxOneWeight(argMultimap); + checkMaxOneGrade(argMultimap); + checkUniqueNotNUllName(argMultimap); + checkUniqueNotNullIndex(argMultimap); + + Index index = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INDEX).get()); + List nameKeywords = argMultimap.getAllValues(PREFIX_NAME); + // for all the names, trim the name and only take the first word + for (int i = 0; i < nameKeywords.size(); i++) { + String name = nameKeywords.get(i); + name = name.trim(); + if (name.trim().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + UpdateExamCommand.MESSAGE_USAGE)); + } + nameKeywords.set(i, name); + } + names = nameKeywords; + + if (nameKeywords.size() > 1) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + "Only one name is allowed for update exam command.")); + } + + // if exam name is not present, set it to null, else parse it + Optional examName = Optional.empty(); + if (argMultimap.getValue(PREFIX_EXAM).isPresent()) { + examName = Optional.of(argMultimap.getValue(PREFIX_EXAM).get()); + } + + // if start time is not present, set it to null, else parse it + Optional startTime = Optional.empty(); + if (argMultimap.getValue(PREFIX_STARTTIME).isPresent()) { + startTime = Optional.of(ParserUtil.parseStartTime(argMultimap.getValue(PREFIX_STARTTIME).get())); + } + + // if end time is not present, set it to null, else parse it + Optional endTime = Optional.empty(); + if (argMultimap.getValue(PREFIX_ENDTIME).isPresent()) { + endTime = Optional.of(ParserUtil.parseEndTime(argMultimap.getValue(PREFIX_ENDTIME).get())); + } + + Optional weight = Optional.empty(); + if (argMultimap.getValue(PREFIX_WEIGHT).isPresent()) { + weight = Optional.of(ParserUtil.parseWeightage(argMultimap.getValue(PREFIX_WEIGHT).get())); + } + + Optional grade = Optional.empty(); + if (argMultimap.getValue(PREFIX_GRADE).isPresent()) { + grade = Optional.of(ParserUtil.parseGrade(argMultimap.getValue(PREFIX_GRADE).get())); + } + + return new UpdateExamCommand(names, index, new NamePredicate(nameKeywords), + examName, startTime, endTime, weight, grade); + } + + /** + * Returns true if all prefixes are present in the given {@code ArgumentMultimap}. + * + * @param argumentMultimap the argument multimap to check for prefixes. + * @param prefixes the prefixes to be checked. + * @return true if all prefixes are present in the given {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/exam/ViewExamCommandParser.java b/src/main/java/seedu/address/logic/parser/exam/ViewExamCommandParser.java new file mode 100644 index 00000000000..440ab39623c --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/exam/ViewExamCommandParser.java @@ -0,0 +1,110 @@ +package seedu.address.logic.parser.exam; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EXAM; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneDate; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneDone; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneExam; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNUllName; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.logic.commands.exam.ViewExamCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.student.Exam; +import seedu.address.model.student.ExamDatePredicate; +import seedu.address.model.student.ExamDonePredicate; +import seedu.address.model.student.ExamPredicate; +import seedu.address.model.student.NamePredicate; +import seedu.address.model.student.Student; + +/** + * Parses input arguments and creates a new ViewExamCommand object + */ +public class ViewExamCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the ViewExamCommand + * and returns an ViewExamCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public ViewExamCommand parse(String args) throws ParseException { + requireNonNull(args); + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_DATE, PREFIX_EXAM, + PREFIX_DONE); + + if (!argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ViewExamCommand.MESSAGE_USAGE)); + } + + Predicate namePredicate; + Predicate examPredicate = exam -> true; + Predicate donePredicate = exam -> true; + boolean defaultPredicateFlag; + List nameList = new ArrayList<>(); + + // If name is present, create a predicate to filter by name + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + checkUniqueNotNUllName(argMultimap); + List nameKeywords = argMultimap.getAllValues(PREFIX_NAME); + // for all the names, trim the name and only take the first word + for (int i = 0; i < nameKeywords.size(); i++) { + String name = nameKeywords.get(i); + name = name.trim(); + if (name.trim().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ViewExamCommand.MESSAGE_USAGE)); + } + nameKeywords.set(i, name); + } + nameList = nameKeywords; + namePredicate = new NamePredicate(nameKeywords); + defaultPredicateFlag = false; + } else { + namePredicate = PREDICATE_SHOW_ALL_STUDENTS; + defaultPredicateFlag = true; + } + + if (argMultimap.getValue(PREFIX_EXAM).isPresent()) { + checkMaxOneExam(argMultimap); + String exam = argMultimap.getValue(PREFIX_EXAM).get(); + examPredicate = new ExamPredicate(exam); + } + + if (argMultimap.getValue(PREFIX_DONE).isPresent()) { + checkMaxOneDone(argMultimap); + String done = argMultimap.getValue(PREFIX_DONE).get(); + if (!done.equals("done") && !done.equals("not done")) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ViewExamCommand.MESSAGE_USAGE)); + } + donePredicate = new ExamDonePredicate(done); + } + + // If date is present, create a predicate to filter by date + if (argMultimap.getValue(PREFIX_DATE).isPresent()) { + checkMaxOneDate(argMultimap); + String date = argMultimap.getValue(PREFIX_DATE).get(); + LocalDate targetDate = ParserUtil.parseDate(date); + ExamDatePredicate datePredicate = new ExamDatePredicate(targetDate); + return new ViewExamCommand(nameList, namePredicate, datePredicate, examPredicate, donePredicate, + defaultPredicateFlag); + } else { + return new ViewExamCommand(nameList, namePredicate, examPredicate, donePredicate, defaultPredicateFlag); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/homework/CreateHomeworkCommandParser.java b/src/main/java/seedu/address/logic/parser/homework/CreateHomeworkCommandParser.java new file mode 100644 index 00000000000..2f7476d2b18 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/homework/CreateHomeworkCommandParser.java @@ -0,0 +1,76 @@ +package seedu.address.logic.parser.homework; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_HOMEWORK; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.ParserUtil.checkNotNullNames; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNUllDeadline; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNUllHomework; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Stream; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.homework.CreateHomeworkCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.student.NamePredicate; + +/** + * Parses input arguments and creates a new CreateHomeworkCommand object + */ +public class CreateHomeworkCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the CreateHomeworkCommand + * and returns a CreateHomeworkCommand object for execution. + * + * @param args the user input to be parsed. + * @return CreateHomeworkCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public CreateHomeworkCommand parse(String args) throws ParseException { + requireNonNull(args); + + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_HOMEWORK, PREFIX_DEADLINE); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_HOMEWORK, PREFIX_DEADLINE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CreateHomeworkCommand.MESSAGE_USAGE)); + } + + List nameKeywords = argMultimap.getAllValues(PREFIX_NAME); + + checkNotNullNames(nameKeywords); + checkUniqueNotNUllHomework(argMultimap); + checkUniqueNotNUllDeadline(argMultimap); + + String homeworkName = argMultimap.getValue(PREFIX_HOMEWORK).get(); + LocalDateTime deadline = ParserUtil.parseDeadline(argMultimap.getValue(PREFIX_DEADLINE).get()); + if (deadline.isBefore(LocalDateTime.now())) { + throw new ParseException(Messages.MESSAGE_DEADLINE_IN_PAST); + } + + return new CreateHomeworkCommand(nameKeywords, new NamePredicate(nameKeywords), + homeworkName, deadline); + } + + /** + * Returns true if all prefixes are present in the given {@code ArgumentMultimap}. + * + * @param argumentMultimap the argument multimap to check for prefixes. + * @param prefixes the prefixes to be checked. + * @return true if all prefixes are present in the given {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/homework/DeleteHomeworkCommandParser.java b/src/main/java/seedu/address/logic/parser/homework/DeleteHomeworkCommandParser.java new file mode 100644 index 00000000000..98cfae441f4 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/homework/DeleteHomeworkCommandParser.java @@ -0,0 +1,69 @@ +package seedu.address.logic.parser.homework; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNUllName; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNullIndex; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.homework.DeleteHomeworkCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.student.NamePredicate; + +/** + * Parses input arguments and creates a new CreateHomeworkCommand object + */ +public class DeleteHomeworkCommandParser implements Parser { + private List names = new ArrayList<>(); + /** + * Parses the given {@code String} of arguments in the context of the CreateHomeworkCommand + * and returns a CreateHomeworkCommand object for execution. + * + * @param args the user input arguments to be parsed. + * @return CreateHomeworkCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteHomeworkCommand parse(String args) throws ParseException { + requireNonNull(args); + + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_INDEX); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_INDEX) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteHomeworkCommand.MESSAGE_USAGE)); + } + + checkUniqueNotNUllName(argMultimap); + checkUniqueNotNullIndex(argMultimap); + + List nameKeywords = argMultimap.getAllValues(PREFIX_NAME); + names = nameKeywords; + Index index = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INDEX).get()); + + return new DeleteHomeworkCommand(names, new NamePredicate(nameKeywords), index); + } + + /** + * Returns true if all prefixes are present in the given {@code ArgumentMultimap}. + * + * @param argumentMultimap the argument multimap to be checked. + * @param prefixes the prefixes to be checked. + * @return true if all prefixes are present in the given {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/homework/MarkHomeworkAsDoneCommandParser.java b/src/main/java/seedu/address/logic/parser/homework/MarkHomeworkAsDoneCommandParser.java new file mode 100644 index 00000000000..d5241e6438e --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/homework/MarkHomeworkAsDoneCommandParser.java @@ -0,0 +1,69 @@ +package seedu.address.logic.parser.homework; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNUllName; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNullIndex; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.homework.MarkHomeworkAsDoneCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.student.NamePredicate; + +/** + * Parses input arguments and creates a new MarkHomeworkAsDoneCommand object + */ +public class MarkHomeworkAsDoneCommandParser implements Parser { + private List names = new ArrayList<>(); + /** + * Parses the given {@code String} of arguments in the context of the MarkHomeworkAsDoneCommand + * and returns a MarkHomeworkAsDoneCommand object for execution. + * + * @param args the user input to be parsed. + * @return MarkHomeworkAsDoneCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public MarkHomeworkAsDoneCommand parse(String args) throws ParseException { + requireNonNull(args); + + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_INDEX); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_INDEX) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + MarkHomeworkAsDoneCommand.MESSAGE_USAGE)); + } + + checkUniqueNotNUllName(argMultimap); + checkUniqueNotNullIndex(argMultimap); + + List nameKeywords = argMultimap.getAllValues(PREFIX_NAME); + names = nameKeywords; + Index index = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INDEX).get()); + + return new MarkHomeworkAsDoneCommand(names, new NamePredicate(nameKeywords), index); + } + + /** + * Returns true if all prefixes are present in the given {@code ArgumentMultimap}. + * + * @param argumentMultimap the argument multimap to check for prefixes. + * @param prefixes the prefixes to be checked. + * @return true if all prefixes are present in the given {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/homework/MarkHomeworkAsUndoCommandParser.java b/src/main/java/seedu/address/logic/parser/homework/MarkHomeworkAsUndoCommandParser.java new file mode 100644 index 00000000000..01151aecc38 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/homework/MarkHomeworkAsUndoCommandParser.java @@ -0,0 +1,69 @@ +package seedu.address.logic.parser.homework; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNUllName; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNullIndex; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.homework.MarkHomeworkAsUndoCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.student.NamePredicate; + +/** + * Parses input arguments and creates a new MarkHomeworkAsDoneCommand object + */ +public class MarkHomeworkAsUndoCommandParser implements Parser { + private List names = new ArrayList<>(); + /** + * Parses the given {@code String} of arguments in the context of the MarkHomeworkAsDoneCommand + * and returns a MarkHomeworkAsDoneCommand object for execution. + * + * @param args the user input to be parsed. + * @return MarkHomeworkAsUndoCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public MarkHomeworkAsUndoCommand parse(String args) throws ParseException { + requireNonNull(args); + + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_INDEX); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_INDEX) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + MarkHomeworkAsUndoCommand.MESSAGE_USAGE)); + } + + checkUniqueNotNUllName(argMultimap); + checkUniqueNotNullIndex(argMultimap); + + List nameKeywords = argMultimap.getAllValues(PREFIX_NAME); + names = nameKeywords; + Index index = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INDEX).get()); + + return new MarkHomeworkAsUndoCommand(names, new NamePredicate(nameKeywords), index); + } + + /** + * Returns true if all prefixes are present in the given {@code ArgumentMultimap}. + * + * @param argumentMultimap the argument multimap to check for prefixes. + * @param prefixes the prefixes to be checked. + * @return true if all prefixes are present in the given {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/homework/UpdateHomeworkCommandParser.java b/src/main/java/seedu/address/logic/parser/homework/UpdateHomeworkCommandParser.java new file mode 100644 index 00000000000..4f8179dd942 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/homework/UpdateHomeworkCommandParser.java @@ -0,0 +1,91 @@ +package seedu.address.logic.parser.homework; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_HOMEWORK; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.ParserUtil.checkNotNullHomework; +import static seedu.address.logic.parser.ParserUtil.checkUniqueHomework; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNUllDeadline; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNUllName; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNullIndex; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.homework.UpdateHomeworkCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.student.NamePredicate; + +/** + * An UpdateHomeworkCommandParser that parses input arguments and creates a new UpdateHomeworkCommand object + */ +public class UpdateHomeworkCommandParser implements Parser { + private List names = new ArrayList<>(); + /** + * Parses the given {@code String} of arguments in the context of the UpdateHomeworkCommand + * and returns an UpdateHomeworkCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public UpdateHomeworkCommand parse(String args) throws ParseException { + requireNonNull(args); + + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_INDEX, PREFIX_HOMEWORK, PREFIX_DEADLINE); + + if ((!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_INDEX, PREFIX_HOMEWORK) + && !arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_INDEX, PREFIX_DEADLINE)) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + UpdateHomeworkCommand.MESSAGE_USAGE)); + } + + checkUniqueNotNUllName(argMultimap); + checkUniqueNotNullIndex(argMultimap); + + List nameKeywords = argMultimap.getAllValues(PREFIX_NAME); + names = nameKeywords; + Index index = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INDEX).get()); + + // if homework name is not present, set it to null, else parse it + Optional homeworkName = Optional.empty(); + if (argMultimap.getValue(PREFIX_HOMEWORK).isPresent()) { + checkUniqueHomework(argMultimap); + homeworkName = Optional.of(argMultimap.getValue(PREFIX_HOMEWORK).get()); + checkNotNullHomework(homeworkName.get()); + } + + // if deadline is not present, set it to null, else parse it + Optional deadline = Optional.empty(); + if (argMultimap.getValue(PREFIX_DEADLINE).isPresent()) { + checkUniqueNotNUllDeadline(argMultimap); + deadline = Optional.of(ParserUtil.parseDeadline(argMultimap.getValue(PREFIX_DEADLINE).get())); + } + + return new UpdateHomeworkCommand(names, index, new NamePredicate(nameKeywords), + homeworkName, deadline); + } + + /** + * Returns true if all prefixes are present in the given {@code ArgumentMultimap}. + * + * @param argumentMultimap the argument multimap to check for prefixes. + * @param prefixes the prefixes to be checked. + * @return true if all prefixes are present in the given {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/homework/ViewHomeworkCommandParser.java b/src/main/java/seedu/address/logic/parser/homework/ViewHomeworkCommandParser.java new file mode 100644 index 00000000000..5145f7c140a --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/homework/ViewHomeworkCommandParser.java @@ -0,0 +1,92 @@ +package seedu.address.logic.parser.homework; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STATUS; +import static seedu.address.logic.parser.ParserUtil.checkNotNullNames; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNullStatus; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.logic.commands.homework.ViewHomeworkCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.student.HomeworkIsCompletePredicate; +import seedu.address.model.student.NamePredicate; +import seedu.address.model.student.Student; + +/** + * Parses input arguments and creates a new ViewHomeworkCommand object + */ +public class ViewHomeworkCommandParser implements Parser { + private List names = new ArrayList<>(); + + /** + * Checks if the list of strings contains an empty string. + * + * @param list the list of strings to be checked. + * @return true if the list does not contain an empty string, false otherwise. + */ + private boolean checkEmptyString(List list) { + for (String s : list) { + if (s.isEmpty()) { + return false; + } + } + return true; + } + + /** + * Parses the given {@code String} of arguments in the context of the ViewHomeworkCommand + * and returns a ViewHomeworkCommand object for execution. + * + * @param args the user input to be parsed into a ViewHomeworkCommand object. + * @return a ViewHomeworkCommand object. + * @throws ParseException if the user input does not conform the expected format + */ + public ViewHomeworkCommand parse(String args) throws ParseException { + requireNonNull(args); + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_STATUS); + + if (!argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ViewHomeworkCommand.MESSAGE_USAGE)); + } + + Predicate namePredicate; + boolean defaultPredicateFlag; + + // If name is present, create a predicate to filter by name + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + List nameKeywords = argMultimap.getAllValues(PREFIX_NAME); + checkNotNullNames(nameKeywords); + + names = nameKeywords; + namePredicate = new NamePredicate(nameKeywords); + defaultPredicateFlag = false; + } else { + namePredicate = PREDICATE_SHOW_ALL_STUDENTS; + defaultPredicateFlag = true; + } + + // If status is present, create a predicate to filter by status + if (argMultimap.getValue(PREFIX_STATUS).isPresent()) { + checkUniqueNotNullStatus(argMultimap); + + String status = argMultimap.getValue(PREFIX_STATUS).get(); + boolean isCompleted = ParserUtil.parseStatus(status); + HomeworkIsCompletePredicate statusPredicate = new HomeworkIsCompletePredicate(isCompleted); + return new ViewHomeworkCommand(names, namePredicate, statusPredicate, defaultPredicateFlag); + } else { + return new ViewHomeworkCommand(names, namePredicate, defaultPredicateFlag); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/lesson/CreateLessonCommandParser.java b/src/main/java/seedu/address/logic/parser/lesson/CreateLessonCommandParser.java new file mode 100644 index 00000000000..28b51545eb4 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/lesson/CreateLessonCommandParser.java @@ -0,0 +1,90 @@ +package seedu.address.logic.parser.lesson; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ENDTIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LESSON; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STARTTIME; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneEndTime; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneLesson; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneStartTime; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNUllName; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import seedu.address.logic.commands.lesson.CreateLessonCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.student.NamePredicate; + +/** + * Parses input arguments and creates a new CreateLessonCommand object + */ +public class CreateLessonCommandParser implements Parser { + private List names = new ArrayList<>(); + /** + * Parses the given {@code String} of arguments in the context of the CreateLessonCommand + * and returns an CreateLessonCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public CreateLessonCommand parse(String args) throws ParseException { + requireNonNull(args); + + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_LESSON, PREFIX_STARTTIME, PREFIX_ENDTIME); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_LESSON, PREFIX_STARTTIME, PREFIX_ENDTIME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CreateLessonCommand.MESSAGE_USAGE)); + } + checkUniqueNotNUllName(argMultimap); + checkMaxOneStartTime(argMultimap); + checkMaxOneEndTime(argMultimap); + checkMaxOneLesson(argMultimap); + + String lessonName = argMultimap.getValue(PREFIX_LESSON).get(); + if (lessonName.trim().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CreateLessonCommand.MESSAGE_USAGE)); + } + LocalDateTime startTime = ParserUtil.parseDeadline(argMultimap.getValue(PREFIX_STARTTIME).get()); + LocalDateTime endTime = ParserUtil.parseDeadline(argMultimap.getValue(PREFIX_ENDTIME).get()); + List nameKeywords = argMultimap.getAllValues(PREFIX_NAME); + + if (endTime.isBefore(startTime)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CreateLessonCommand.MESSAGE_DATE)); + } + + // for all the names, trim the name and only take the first word + for (int i = 0; i < nameKeywords.size(); i++) { + String name = nameKeywords.get(i); + if (name.trim().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + CreateLessonCommand.MESSAGE_USAGE)); + } + name = name.trim(); + nameKeywords.set(i, name); + } + names = nameKeywords; + + return new CreateLessonCommand(names, new NamePredicate(nameKeywords), + lessonName, startTime, endTime); + } + + /** + * Returns true if all prefixes are present in the given {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/lesson/DeleteLessonCommandParser.java b/src/main/java/seedu/address/logic/parser/lesson/DeleteLessonCommandParser.java new file mode 100644 index 00000000000..742382a7b02 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/lesson/DeleteLessonCommandParser.java @@ -0,0 +1,73 @@ +package seedu.address.logic.parser.lesson; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNUllName; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNullIndex; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.lesson.DeleteLessonCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.student.NamePredicate; + +/** + * Parses input arguments and creates a new DeleteLessonCommand object + */ +public class DeleteLessonCommandParser implements Parser { + private List inputNames = new ArrayList<>(); + /** + * Parses the given {@code String} of arguments in the context of the DeleteLessonCommand + * and returns a DeleteLessonCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteLessonCommand parse(String args) throws ParseException { + requireNonNull(args); + + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_INDEX); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_INDEX) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteLessonCommand.MESSAGE_USAGE)); + } + + checkUniqueNotNullIndex(argMultimap); + checkUniqueNotNUllName(argMultimap); + + List nameKeywords = argMultimap.getAllValues(PREFIX_NAME); + // for all the names, trim the name and only take the first word + for (int i = 0; i < nameKeywords.size(); i++) { + String name = nameKeywords.get(i); + name = name.trim(); + if (name.trim().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteLessonCommand.MESSAGE_USAGE)); + } + nameKeywords.set(i, name); + } + inputNames = nameKeywords; + + Index index = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INDEX).get()); + return new DeleteLessonCommand(inputNames, new NamePredicate(nameKeywords), index); + } + + /** + * Returns true if all prefixes are present in the given {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/lesson/UpdateLessonCommandParser.java b/src/main/java/seedu/address/logic/parser/lesson/UpdateLessonCommandParser.java new file mode 100644 index 00000000000..8a75d79e5f6 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/lesson/UpdateLessonCommandParser.java @@ -0,0 +1,115 @@ +package seedu.address.logic.parser.lesson; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ENDTIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_INDEX; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LESSON; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_STARTTIME; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneEndTime; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneLesson; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneStartTime; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNUllName; +import static seedu.address.logic.parser.ParserUtil.checkUniqueNotNullIndex; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.lesson.UpdateLessonCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.student.NamePredicate; + +/** + * Parses input arguments and creates a new UpdateLessonCommand object + */ +public class UpdateLessonCommandParser implements Parser { + private List names = new ArrayList<>(); + /** + * Parses the given {@code String} of arguments in the context of the UpdateLessonCommand + * and returns an UpdateLessonCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public UpdateLessonCommand parse(String args) throws ParseException { + requireNonNull(args); + + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_INDEX, PREFIX_LESSON, PREFIX_STARTTIME, + PREFIX_ENDTIME); + + if ((!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_INDEX, PREFIX_LESSON) + && !arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_INDEX, PREFIX_STARTTIME) + && !arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_INDEX, PREFIX_ENDTIME)) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + UpdateLessonCommand.MESSAGE_USAGE)); + } + + checkUniqueNotNUllName(argMultimap); + checkUniqueNotNullIndex(argMultimap); + checkMaxOneStartTime(argMultimap); + checkMaxOneEndTime(argMultimap); + checkMaxOneLesson(argMultimap); + + Index index = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_INDEX).get()); + List nameKeywords = argMultimap.getAllValues(PREFIX_NAME); + // for all the names, trim the name and only take the first word + for (int i = 0; i < nameKeywords.size(); i++) { + String name = nameKeywords.get(i); + name = name.trim(); + if (name.trim().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + UpdateLessonCommand.MESSAGE_USAGE)); + } + nameKeywords.set(i, name); + } + names = nameKeywords; + + if (nameKeywords.size() > 1) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + "Only one name is allowed for update lesson command.")); + } + + // if lesson is not present, set it to null, else parse it + Optional lessonName = Optional.empty(); + if (argMultimap.getValue(PREFIX_LESSON).isPresent()) { + lessonName = Optional.of(argMultimap.getValue(PREFIX_LESSON).get()); + } + + // if start time is not present, set it to null, else parse it + Optional startTime = Optional.empty(); + if (argMultimap.getValue(PREFIX_STARTTIME).isPresent()) { + startTime = Optional.of(ParserUtil.parseStartTime(argMultimap.getValue(PREFIX_STARTTIME).get())); + } + + // if end time is not present, set it to null, else parse it + Optional endTime = Optional.empty(); + if (argMultimap.getValue(PREFIX_ENDTIME).isPresent()) { + endTime = Optional.of(ParserUtil.parseEndTime(argMultimap.getValue(PREFIX_ENDTIME).get())); + } + + return new UpdateLessonCommand(names, index, new NamePredicate(nameKeywords), + lessonName, startTime, endTime); + } + + /** + * Returns true if all prefixes are present in the given {@code ArgumentMultimap}. + * + * @param argumentMultimap the argument multimap to check for prefixes. + * @param prefixes the prefixes to be checked. + * @return true if all prefixes are present in the given {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/lesson/ViewLessonCommandParser.java b/src/main/java/seedu/address/logic/parser/lesson/ViewLessonCommandParser.java new file mode 100644 index 00000000000..8df86b8cea8 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/lesson/ViewLessonCommandParser.java @@ -0,0 +1,117 @@ +package seedu.address.logic.parser.lesson; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_LESSON; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneDate; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneDone; +import static seedu.address.logic.parser.ParserUtil.checkMaxOneLesson; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.logic.commands.lesson.ViewLessonCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.student.Lesson; +import seedu.address.model.student.LessonBelongsToDatePredicate; +import seedu.address.model.student.LessonDonePredicate; +import seedu.address.model.student.LessonSubjectPredicate; +import seedu.address.model.student.NamePredicate; +import seedu.address.model.student.Student; + +/** + * Parses input arguments and creates a new ViewLessonCommand object + */ +public class ViewLessonCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the ViewLessonCommand + * and returns an ViewLessonCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public ViewLessonCommand parse(String args) throws ParseException { + requireNonNull(args); + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_DATE, PREFIX_LESSON, + PREFIX_DONE); + + if (!argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ViewLessonCommand.MESSAGE_USAGE)); + } + + Predicate namePredicate; + Predicate lessonPredicate = lesson -> true; + Predicate donePredicate = lesson -> true; + List nameList = new ArrayList<>(); + boolean defaultPredicateFlag; + + // If name is present, create a predicate to filter by name + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + + List nameKeywords = argMultimap.getAllValues(PREFIX_NAME); + // for all the names, trim the name and only take the first word + for (int i = 0; i < nameKeywords.size(); i++) { + String name = nameKeywords.get(i); + name = name.trim(); + if (name.trim().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ViewLessonCommand.MESSAGE_USAGE)); + } + nameKeywords.set(i, name); + } + nameList = nameKeywords; + namePredicate = new NamePredicate(nameKeywords); + defaultPredicateFlag = false; + } else { + namePredicate = PREDICATE_SHOW_ALL_STUDENTS; + defaultPredicateFlag = true; + } + + if (argMultimap.getValue(PREFIX_LESSON).isPresent()) { + checkMaxOneLesson(argMultimap); + String lessonSubject = argMultimap.getValue(PREFIX_LESSON).get(); + lessonPredicate = new LessonSubjectPredicate(lessonSubject); + } + + if (argMultimap.getValue(PREFIX_DONE).isPresent()) { + checkMaxOneDone(argMultimap); + String done = argMultimap.getValue(PREFIX_DONE).get(); + if (!done.equals("done") && !done.equals("not done")) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ViewLessonCommand.MESSAGE_USAGE)); + } + donePredicate = new LessonDonePredicate(done); + } + + // If date is present, create a predicate to filter by status + if (argMultimap.getValue(PREFIX_DATE).isPresent()) { + checkMaxOneDate(argMultimap); + String date = argMultimap.getValue(PREFIX_DATE).get(); + LocalDate targetDate = ParserUtil.parseDate(date); + LessonBelongsToDatePredicate datePredicate = new LessonBelongsToDatePredicate(targetDate); + return new ViewLessonCommand(nameList, namePredicate, datePredicate, lessonPredicate, donePredicate, + defaultPredicateFlag); + } else { + return new ViewLessonCommand(nameList, namePredicate, lessonPredicate, donePredicate, + defaultPredicateFlag); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ViewLessonCommandParser // instanceof handles nulls + && this.equals(other)); // state check + } +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 1a943a0781a..1e6b8b6a66a 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -5,8 +5,8 @@ import java.util.List; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; +import seedu.address.model.student.Student; +import seedu.address.model.student.UniqueStudentList; /** * Wraps all data at the address-book level @@ -14,7 +14,7 @@ */ public class AddressBook implements ReadOnlyAddressBook { - private final UniquePersonList persons; + private final UniqueStudentList students; /* * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication @@ -24,7 +24,7 @@ public class AddressBook implements ReadOnlyAddressBook { * among constructors. */ { - persons = new UniquePersonList(); + students = new UniqueStudentList(); } public AddressBook() {} @@ -43,8 +43,8 @@ public AddressBook(ReadOnlyAddressBook toBeCopied) { * Replaces the contents of the person list with {@code persons}. * {@code persons} must not contain duplicate persons. */ - public void setPersons(List persons) { - this.persons.setPersons(persons); + public void setStudents(List students) { + this.students.setStudents(students); } /** @@ -53,7 +53,7 @@ public void setPersons(List persons) { public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); - setPersons(newData.getPersonList()); + setStudents(newData.getPersonList()); } //// person-level operations @@ -61,17 +61,17 @@ public void resetData(ReadOnlyAddressBook newData) { /** * Returns true if a person with the same identity as {@code person} exists in the address book. */ - public boolean hasPerson(Person person) { + public boolean hasPerson(Student person) { requireNonNull(person); - return persons.contains(person); + return students.contains(person); } /** * Adds a person to the address book. * The person must not already exist in the address book. */ - public void addPerson(Person p) { - persons.add(p); + public void addStudent(Student p) { + students.add(p); } /** @@ -79,42 +79,42 @@ public void addPerson(Person p) { * {@code target} must exist in the address book. * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. */ - public void setPerson(Person target, Person editedPerson) { + public void setPerson(Student target, Student editedPerson) { requireNonNull(editedPerson); - persons.setPerson(target, editedPerson); + students.setPerson(target, editedPerson); } /** * Removes {@code key} from this {@code AddressBook}. * {@code key} must exist in the address book. */ - public void removePerson(Person key) { - persons.remove(key); + public void removePerson(Student key) { + students.remove(key); } //// util methods @Override public String toString() { - return persons.asUnmodifiableObservableList().size() + " persons"; + return students.asUnmodifiableObservableList().size() + " persons"; // TODO: refine later } @Override - public ObservableList getPersonList() { - return persons.asUnmodifiableObservableList(); + public ObservableList getPersonList() { + return students.asUnmodifiableObservableList(); } @Override public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof AddressBook // instanceof handles nulls - && persons.equals(((AddressBook) other).persons)); + && students.equals(((AddressBook) other).students)); } @Override public int hashCode() { - return persons.hashCode(); + return students.hashCode(); } } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..eeabee09e53 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -5,14 +5,15 @@ import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.Person; +import seedu.address.model.student.Lesson; +import seedu.address.model.student.Student; /** - * The API of the Model component. + * The API of the Model component. Contains the data of the application in-memory. */ public interface Model { /** {@code Predicate} that always evaluate to true */ - Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + Predicate PREDICATE_SHOW_ALL_STUDENTS = unused -> true; /** * Replaces user prefs data with the data in {@code userPrefs}. @@ -55,33 +56,43 @@ public interface Model { /** * Returns true if a person with the same identity as {@code person} exists in the address book. */ - boolean hasPerson(Person person); + boolean hasPerson(Student person); /** * Deletes the given person. * The person must exist in the address book. */ - void deletePerson(Person target); + void deletePerson(Student target); /** * Adds the given person. * {@code person} must not already exist in the address book. */ - void addPerson(Person person); + void addPerson(Student person); /** * Replaces the given person {@code target} with {@code editedPerson}. * {@code target} must exist in the address book. * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. */ - void setPerson(Person target, Person editedPerson); + void setStudent(Student target, Student editedPerson); /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); + ObservableList getFilteredStudentList(); /** * Updates the filter of the filtered person list to filter by the given {@code predicate}. * @throws NullPointerException if {@code predicate} is null. */ - void updateFilteredPersonList(Predicate predicate); + void updateFilteredStudentList(Predicate predicate); + + boolean hasDuplicateName(String name); + boolean hasDuplicateNameEdit(String name, Integer index); + boolean hasExtendedName(String name); + boolean hasExtendedNameEdit(String name, Integer index); + boolean noSuchStudent(String name); + boolean hasDuplicateNameAdd(String toString); + boolean hasConflictingLessonTime(Lesson lesson); + + boolean hasConflictingExamTime(Lesson lesson); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 86c1df298d7..a4e760ec699 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -11,7 +11,8 @@ import javafx.collections.transformation.FilteredList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; +import seedu.address.model.student.Lesson; +import seedu.address.model.student.Student; /** * Represents the in-memory model of the address book data. @@ -21,7 +22,7 @@ public class ModelManager implements Model { private final AddressBook addressBook; private final UserPrefs userPrefs; - private final FilteredList filteredPersons; + private final FilteredList filteredPersons; /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -88,24 +89,24 @@ public ReadOnlyAddressBook getAddressBook() { } @Override - public boolean hasPerson(Person person) { + public boolean hasPerson(Student person) { requireNonNull(person); return addressBook.hasPerson(person); } @Override - public void deletePerson(Person target) { + public void deletePerson(Student target) { addressBook.removePerson(target); } @Override - public void addPerson(Person person) { - addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + public void addPerson(Student person) { + addressBook.addStudent(person); + updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); } @Override - public void setPerson(Person target, Person editedPerson) { + public void setStudent(Student target, Student editedPerson) { requireAllNonNull(target, editedPerson); addressBook.setPerson(target, editedPerson); @@ -118,12 +119,12 @@ public void setPerson(Person target, Person editedPerson) { * {@code versionedAddressBook} */ @Override - public ObservableList getFilteredPersonList() { + public ObservableList getFilteredStudentList() { return filteredPersons; } @Override - public void updateFilteredPersonList(Predicate predicate) { + public void updateFilteredStudentList(Predicate predicate) { requireNonNull(predicate); filteredPersons.setPredicate(predicate); } @@ -147,4 +148,94 @@ public boolean equals(Object obj) { && filteredPersons.equals(other.filteredPersons); } + /** + * Returns true if the model has a student whose name is part of the input name. + */ + @Override + public boolean hasDuplicateName(String name) { + int count = 0; + for (Student s : filteredPersons) { + if (s.getName().toString().toLowerCase().contains(name.toLowerCase())) { + count++; + } + } + return count >= 2; + } + + @Override + public boolean hasDuplicateNameAdd(String name) { + int count = 0; + for (Student s : filteredPersons) { + if (s.getName().toString().toLowerCase().contains(name.toLowerCase())) { + count++; + } + } + return count >= 1; + } + + @Override + public boolean hasDuplicateNameEdit(String name, Integer index) { + int count = 0; + for (int i = 0; i < filteredPersons.size(); i++) { + if (filteredPersons.get(i).getName().toString().toLowerCase().contains(name.toLowerCase()) && i != index) { + count++; + } + } + return count >= 1; + } + + /** + * Returns true if the model has a student whose name is part of the input name. + */ + @Override + public boolean hasExtendedName(String name) { + int count = 0; + for (Student s : filteredPersons) { + if (name.toLowerCase().contains(s.getName().toString().toLowerCase())) { + count++; + } + } + return count >= 1; + } + + @Override + public boolean hasExtendedNameEdit(String name, Integer index) { + int count = 0; + for (int i = 0; i < filteredPersons.size(); i++) { + if (name.toLowerCase().contains(filteredPersons.get(i).getName().toString().toLowerCase()) && i != index) { + count++; + } + } + return count >= 1; + } + + @Override + public boolean noSuchStudent(String name) { + for (Student s : filteredPersons) { + if (s.getName().toString().toLowerCase().contains(name.toLowerCase())) { + return false; + } + } + return true; + } + + @Override + public boolean hasConflictingLessonTime(Lesson lesson) { + for (Student s : filteredPersons) { + if (s.hasConflictingLessonTime(lesson)) { + return true; + } + } + return false; + } + + @Override + public boolean hasConflictingExamTime(Lesson lesson) { + for (Student s : filteredPersons) { + if (s.hasConflictingExamTime(lesson)) { + return true; + } + } + return false; + } } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 6ddc2cd9a29..e1c7f661f4d 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -1,7 +1,7 @@ package seedu.address.model; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; +import seedu.address.model.student.Student; /** * Unmodifiable view of an address book @@ -12,6 +12,6 @@ public interface ReadOnlyAddressBook { * Returns an unmodifiable view of the persons list. * This list will not contain any duplicate persons. */ - ObservableList getPersonList(); + ObservableList getPersonList(); } diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java deleted file mode 100644 index 8ff1d83fe89..00000000000 --- a/src/main/java/seedu/address/model/person/Person.java +++ /dev/null @@ -1,123 +0,0 @@ -package seedu.address.model.person; - -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -import seedu.address.model.tag.Tag; - -/** - * Represents a Person in the address book. - * Guarantees: details are present and not null, field values are validated, immutable. - */ -public class Person { - - // Identity fields - private final Name name; - private final Phone phone; - private final Email email; - - // Data fields - private final Address address; - private final Set tags = new HashSet<>(); - - /** - * Every field must be present and not null. - */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - this.tags.addAll(tags); - } - - public Name getName() { - return name; - } - - public Phone getPhone() { - return phone; - } - - public Email getEmail() { - return email; - } - - public Address getAddress() { - return address; - } - - /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - */ - public Set getTags() { - return Collections.unmodifiableSet(tags); - } - - /** - * Returns true if both persons have the same name. - * This defines a weaker notion of equality between two persons. - */ - public boolean isSamePerson(Person otherPerson) { - if (otherPerson == this) { - return true; - } - - return otherPerson != null - && otherPerson.getName().equals(getName()); - } - - /** - * Returns true if both persons have the same identity and data fields. - * This defines a stronger notion of equality between two persons. - */ - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof Person)) { - return false; - } - - Person otherPerson = (Person) other; - return otherPerson.getName().equals(getName()) - && otherPerson.getPhone().equals(getPhone()) - && otherPerson.getEmail().equals(getEmail()) - && otherPerson.getAddress().equals(getAddress()) - && otherPerson.getTags().equals(getTags()); - } - - @Override - public int hashCode() { - // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); - } - - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append(getName()) - .append("; Phone: ") - .append(getPhone()) - .append("; Email: ") - .append(getEmail()) - .append("; Address: ") - .append(getAddress()); - - Set tags = getTags(); - if (!tags.isEmpty()) { - builder.append("; Tags: "); - tags.forEach(builder::append); - } - return builder.toString(); - } - -} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java deleted file mode 100644 index 0fee4fe57e6..00000000000 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ /dev/null @@ -1,137 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Iterator; -import java.util.List; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; - -/** - * A list of persons that enforces uniqueness between its elements and does not allow nulls. - * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, adding and updating of - * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is - * unique in terms of identity in the UniquePersonList. However, the removal of a person uses Person#equals(Object) so - * as to ensure that the person with exactly the same fields will be removed. - * - * Supports a minimal set of list operations. - * - * @see Person#isSamePerson(Person) - */ -public class UniquePersonList implements Iterable { - - private final ObservableList internalList = FXCollections.observableArrayList(); - private final ObservableList internalUnmodifiableList = - FXCollections.unmodifiableObservableList(internalList); - - /** - * Returns true if the list contains an equivalent person as the given argument. - */ - public boolean contains(Person toCheck) { - requireNonNull(toCheck); - return internalList.stream().anyMatch(toCheck::isSamePerson); - } - - /** - * Adds a person to the list. - * The person must not already exist in the list. - */ - public void add(Person toAdd) { - requireNonNull(toAdd); - if (contains(toAdd)) { - throw new DuplicatePersonException(); - } - internalList.add(toAdd); - } - - /** - * Replaces the person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the list. - * The person identity of {@code editedPerson} must not be the same as another existing person in the list. - */ - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - int index = internalList.indexOf(target); - if (index == -1) { - throw new PersonNotFoundException(); - } - - if (!target.isSamePerson(editedPerson) && contains(editedPerson)) { - throw new DuplicatePersonException(); - } - - internalList.set(index, editedPerson); - } - - /** - * Removes the equivalent person from the list. - * The person must exist in the list. - */ - public void remove(Person toRemove) { - requireNonNull(toRemove); - if (!internalList.remove(toRemove)) { - throw new PersonNotFoundException(); - } - } - - public void setPersons(UniquePersonList replacement) { - requireNonNull(replacement); - internalList.setAll(replacement.internalList); - } - - /** - * Replaces the contents of this list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - requireAllNonNull(persons); - if (!personsAreUnique(persons)) { - throw new DuplicatePersonException(); - } - - internalList.setAll(persons); - } - - /** - * Returns the backing list as an unmodifiable {@code ObservableList}. - */ - public ObservableList asUnmodifiableObservableList() { - return internalUnmodifiableList; - } - - @Override - public Iterator iterator() { - return internalList.iterator(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof UniquePersonList // instanceof handles nulls - && internalList.equals(((UniquePersonList) other).internalList)); - } - - @Override - public int hashCode() { - return internalList.hashCode(); - } - - /** - * Returns true if {@code persons} contains only unique persons. - */ - private boolean personsAreUnique(List persons) { - for (int i = 0; i < persons.size() - 1; i++) { - for (int j = i + 1; j < persons.size(); j++) { - if (persons.get(i).isSamePerson(persons.get(j))) { - return false; - } - } - } - return true; - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java deleted file mode 100644 index d7290f59442..00000000000 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ /dev/null @@ -1,11 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same - * identity). - */ -public class DuplicatePersonException extends RuntimeException { - public DuplicatePersonException() { - super("Operation would result in duplicate persons"); - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java deleted file mode 100644 index fa764426ca7..00000000000 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ /dev/null @@ -1,6 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation is unable to find the specified person. - */ -public class PersonNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/student/Address.java similarity index 97% rename from src/main/java/seedu/address/model/person/Address.java rename to src/main/java/seedu/address/model/student/Address.java index 60472ca22a0..5e04019bde5 100644 --- a/src/main/java/seedu/address/model/person/Address.java +++ b/src/main/java/seedu/address/model/student/Address.java @@ -1,4 +1,4 @@ -package seedu.address.model.person; +package seedu.address.model.student; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/student/Email.java similarity index 96% rename from src/main/java/seedu/address/model/person/Email.java rename to src/main/java/seedu/address/model/student/Email.java index f866e7133de..51b59860552 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/seedu/address/model/student/Email.java @@ -1,10 +1,10 @@ -package seedu.address.model.person; +package seedu.address.model.student; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's email in the address book. + * Represents a Student's email in the address book. * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} */ public class Email { diff --git a/src/main/java/seedu/address/model/student/Exam.java b/src/main/java/seedu/address/model/student/Exam.java new file mode 100644 index 00000000000..ee9c381d471 --- /dev/null +++ b/src/main/java/seedu/address/model/student/Exam.java @@ -0,0 +1,234 @@ +package seedu.address.model.student; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Objects; + +/** + * Represents an Exam in the TutorPro. + * Guarantees: details are present and not null, field values are validated, immutable. + * Note: This class has a natural ordering that is inconsistent with equals. + * This is because the natural ordering is based on the start time of the exam, + * while equals is based on the description, start time, end time, weightage, status, and grade. + */ +public class Exam { + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + private final String description; + private final LocalDateTime startTime; + private final LocalDateTime endTime; + private final Double weightage; + private final Grade grade; + + /** + * Creates a new Exam with the given description, start time, end time, weightage, and grade. + * + * @param description The description of the exam. + * @param startTime The start time of the exam. + * @param endTime The end time of the exam. + * @param weightage The weightage of the exam. + * @param grade The grade of the exam. + */ + public Exam(String description, LocalDateTime startTime, LocalDateTime endTime, + Double weightage, Grade grade) { + Objects.requireNonNull(description); + Objects.requireNonNull(startTime); + Objects.requireNonNull(endTime); + + if (startTime.isAfter(endTime)) { + throw new IllegalArgumentException("Start time cannot be after end time."); + } + + if (weightage != null) { + if (weightage < 0 || weightage > 100) { + throw new IllegalArgumentException("Weightage must be a percentage between 0 and 100."); + } + } + + this.description = description; + this.startTime = startTime; + this.endTime = endTime; + this.weightage = weightage; + this.grade = grade; + } + + /** + * Returns the description of the exam. + * + * @return The description of the exam. + */ + public String getDescription() { + return description; + } + + /** + * Returns the start time of the exam. + * Note: This method is used for sorting exams by start time. + * + * @return The start time of the exam. + */ + public LocalDateTime getStartTime() { + return startTime; + } + + /** + * Returns the end time of the exam. + * + * @return The end time of the exam. + */ + public LocalDateTime getEndTime() { + return endTime; + } + + /** + * Returns the weightage of the exam. + * + * @return The weightage of the exam. + */ + public Double getWeightage() { + return weightage; + } + + public String getStringWeightage() { + if (weightage == null) { + return "Undefined"; + } + return String.format("%.2f", weightage) + "%"; + } + + /** + * Returns the duration of the exam in minutes. + * + * @return The duration of the exam in minutes. + */ + public int getDurationInMinutes() { + return (int) java.time.Duration.between(startTime, endTime).toMinutes(); + } + + /** + * Returns the grade of the exam. + * + * @return The grade of the exam. + * @throws UnsupportedOperationException If the exam is not finished. + */ + public Grade getGrade() { + return grade; + } + + public String getStringGrade() { + if (grade == null) { + return "Undefined"; + } + return grade.toString(); + } + + /** + * Returns true if both exams have the same description and start time. + * This defines a weaker notion of equality between two exams. + * + * @param otherExam The other exam to compare with. + * @return True if both exams have the same description and start time. + */ + public boolean isSameExam(Exam otherExam) { + if (otherExam == this) { + return true; + } + + return otherExam != null + && otherExam.getDescription().equals(getDescription()) + && otherExam.getStartTime().equals(getStartTime()) + && otherExam.getEndTime().equals(getEndTime()) + && compareWeightages(otherExam.getWeightage(), getWeightage()) + && compareGrades(otherExam.getGrade(), getGrade()); + } + + private boolean compareWeightages(Double first, Double second) { + if (first == null && second == null) { + return true; + } else { + if (first == null || second == null) { + return false; + } else { + return Double.compare(first, second) == 0; + } + } + } + + private boolean compareGrades(Grade first, Grade second) { + if (first == null && second == null) { + return true; + } else { + if (first == null || second == null) { + return false; + } else { + return first.equals(second); + } + } + } + + /** + * Returns true if both exams have the same description, start time, end time, weightage, and grade. + * @param otherExam The other exam to compare with. + * @return True if both exams have the same description, start time, end time, weightage, and grade. + */ + public boolean isSameTimeExam(Exam otherExam) { + if (otherExam == this) { + return true; + } + + return otherExam != null + && otherExam.getStartTime().isBefore(getEndTime()) && otherExam.getEndTime().isAfter(getStartTime()); + } + + /** + * Creates a new Exam with the given description, start time, end time, weightage, and status. + * + * @return The new Exam. + */ + public boolean isPastExam() { + return LocalDateTime.now().isAfter(endTime); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Exam)) { + return false; + } + + Exam otherExam = (Exam) other; + + return otherExam.getDescription().equals(getDescription()) + && otherExam.getStartTime().equals(getStartTime()) + && otherExam.getEndTime().equals(getEndTime()) + && otherExam.getWeightage() == getWeightage() + && otherExam.getGrade().equals(getGrade()); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getDescription()) + .append(" Start Time: ") + .append(getStartTime().format(formatter)) + .append(" End Time: ") + .append(getEndTime().format(formatter)) + .append(" Weightage: ") + .append(getStringWeightage()) + .append(" Grade: ") + .append(getStringGrade()); + return builder.toString(); + } + + /** + * Returns true if the given lesson is the same as this one. + * @param lesson The other lesson to compare with. + * @return True if the given lesson has the same time slot as this one. + */ + public boolean isSameTimeLesson(Lesson lesson) { + return lesson != null + && lesson.getStartTime().isBefore(getEndTime()) && lesson.getEndTime().isAfter(getStartTime()); + } +} diff --git a/src/main/java/seedu/address/model/student/ExamDatePredicate.java b/src/main/java/seedu/address/model/student/ExamDatePredicate.java new file mode 100644 index 00000000000..c671e1f513a --- /dev/null +++ b/src/main/java/seedu/address/model/student/ExamDatePredicate.java @@ -0,0 +1,36 @@ +package seedu.address.model.student; + +import java.time.LocalDate; +import java.util.function.Predicate; + +/** + * Tests that a {@code Exam}'s date matches the given date. + */ +public class ExamDatePredicate implements Predicate { + private final LocalDate targetDate; + + /** + * Creates a predicate to test if a {@code Exam}'s {@code isCompleted} matches the given boolean. + * @param targetDate The LocalDate to test against. + */ + public ExamDatePredicate(LocalDate targetDate) { + this.targetDate = targetDate; + } + + /** + * Tests if a {@code Exam}'s date matches the given date. + * @param lesson The exam to test. + * @return True if the exam's date matches the given date. + */ + @Override + public boolean test(Exam lesson) { + return lesson.getStartTime().toLocalDate().isEqual(targetDate); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ExamDatePredicate // instanceof handles nulls + && targetDate == ((ExamDatePredicate) other).targetDate); // date check + } +} diff --git a/src/main/java/seedu/address/model/student/ExamDonePredicate.java b/src/main/java/seedu/address/model/student/ExamDonePredicate.java new file mode 100644 index 00000000000..dc9fbaf5ab2 --- /dev/null +++ b/src/main/java/seedu/address/model/student/ExamDonePredicate.java @@ -0,0 +1,40 @@ +package seedu.address.model.student; + +import java.time.LocalDateTime; +import java.util.function.Predicate; + +/** + * Tests that an {@code Exam} is done or otherwise. + */ +public class ExamDonePredicate implements Predicate { + private final String done; + + /** + * Creates a predicate to test if an Exam is done or otherwise + * @param done The String that represents done-ness (valid values are "done" or "not done") + */ + public ExamDonePredicate(String done) { + this.done = done; + } + + /** + * Tests if an {@code Exam}'s {@code isCompleted} matches the given boolean. + * + * @param exam The Exam to test. + * @return True if the exam has been completed. + */ + @Override + public boolean test(Exam exam) { + if (done.equals("done")) { + return exam.getStartTime().isBefore(LocalDateTime.now()); + } + return exam.getStartTime().isAfter((LocalDateTime.now())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ExamDonePredicate // instanceof handles nulls + && done.equals(((ExamDonePredicate) other).done)); + } +} diff --git a/src/main/java/seedu/address/model/student/ExamPredicate.java b/src/main/java/seedu/address/model/student/ExamPredicate.java new file mode 100644 index 00000000000..41bfaca9a50 --- /dev/null +++ b/src/main/java/seedu/address/model/student/ExamPredicate.java @@ -0,0 +1,37 @@ +package seedu.address.model.student; + +import java.util.function.Predicate; + +/** + * Tests that a {@code Exam}'s title matches the given title. + */ +public class ExamPredicate implements Predicate { + private final String examName; + + /** + * Creates a ExamPredicate to test if a {@code Exam}'s title matches the given title. + * + * @param examName The title to test against. + */ + public ExamPredicate(String examName) { + this.examName = examName; + } + + /** + * Tests if a {@code Exam}'s title matches the given title. + * + * @param exam The exam to test. + * @return True if the exam's title matches the given title. + */ + @Override + public boolean test(Exam exam) { + return exam.getDescription().contains(examName); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ExamPredicate // instanceof handles nulls + && examName.equals(((ExamPredicate) other).examName)); + } +} diff --git a/src/main/java/seedu/address/model/student/Grade.java b/src/main/java/seedu/address/model/student/Grade.java new file mode 100644 index 00000000000..f1f839a4edb --- /dev/null +++ b/src/main/java/seedu/address/model/student/Grade.java @@ -0,0 +1,103 @@ +package seedu.address.model.student; + +import static java.util.Objects.requireNonNull; + +/** + * Represents a Student's grade in the TutorPro. + * Guarantees: details are present and not null, field values are validated, immutable. + * Note: This class has a natural ordering that is inconsistent with equals. + * This is because the natural ordering is based on the percentage score obtained, + * while equals is based on the score and full marks. + */ +public class Grade { + private final double score; + private final double fullMarks; + + /** + * Creates a new Grade with the given score and full marks. + * + * @param score the score obtained by the student + * @param fullMarks the maximum score that could be obtained + * @throws IllegalArgumentException if the score is negative or greater than full marks + */ + public Grade(double score, double fullMarks) throws IllegalArgumentException { + if (score < 0 || score > fullMarks) { + throw new IllegalArgumentException("Invalid score, score has to be positive and lesser than the " + + "full marks achievable"); + } + if (fullMarks <= 0) { + throw new IllegalArgumentException("Full marks must be positive!"); + } + + this.score = score; + this.fullMarks = fullMarks; + } + + //Dummy code that needs to be included for json-related classes to work, I don't know why, todo understand this + //https://stackoverflow.com/questions/7625783/jsonmappingexception-no-suitable-constructor- + // found-for-type-simple-type-class + + /** + * dummy code + */ + public Grade() { + score = 0; + fullMarks = 0; + } + + /** + * Returns the score obtained. + * The score is guaranteed to be non-negative and less than or equal to the full marks. + * + * @return the score obtained + */ + public double getScore() { + return score; + } + + /** + * Returns the full marks. + * The full marks is guaranteed to be non-negative. + * + * @return the full marks + */ + public double getFullMarks() { + return fullMarks; + } + + /** + * Calculates and returns the percentage score obtained. + * The percentage score is guaranteed to be non-negative and less than or equal to 100. + * + * @return the percentage score obtained + */ + public double getPercentageScore() { + return score / fullMarks * 100; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (!(other instanceof Grade)) { + return false; + } + + Grade otherGrade = (Grade) other; + return Double.compare(otherGrade.score, score) == 0 + && Double.compare(otherGrade.fullMarks, fullMarks) == 0; + } + + @Override + public int hashCode() { + return requireNonNull(Double.valueOf(score)).hashCode() + + requireNonNull(Double.valueOf(fullMarks)).hashCode(); + } + + @Override + public String toString() { + return String.format("%.2f / %.2f (%.2f%%)", score, fullMarks, getPercentageScore()); + } +} diff --git a/src/main/java/seedu/address/model/student/Homework.java b/src/main/java/seedu/address/model/student/Homework.java new file mode 100644 index 00000000000..6cf204e9808 --- /dev/null +++ b/src/main/java/seedu/address/model/student/Homework.java @@ -0,0 +1,131 @@ +package seedu.address.model.student; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Objects; + +/** + * Represents a Student's assignment in the TutorPro. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Homework { + private static final DateTimeFormatter PRINT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + private static final String TO_STRING_FORMAT = "Status: %s, Description: %s, Deadline: %s"; + private static final String COMPLETED_TAG = "[X]"; + private static final String PENDING_TAG = "[ ]"; + + private final String description; + private final LocalDateTime deadline; + private Status status; + + /** + * The status of the homework. + */ + public enum Status { + COMPLETED, PENDING; + }; + + /** + * Creates a new Homework with the given description and deadline. + * + * @param description The description of the assignment. + * @param deadline The deadline of the assignment. + */ + public Homework(String description, LocalDateTime deadline) { + Objects.requireNonNull(description); + Objects.requireNonNull(deadline); + + this.description = description; + this.deadline = deadline; + this.status = Status.PENDING; + } + + public String getDescription() { + return description; + } + + public LocalDateTime getDeadline() { + return deadline; + } + + public Status getStatus() { + return status; + } + + public boolean isCompleted() { + return status == Status.COMPLETED; + } + + /** + * Marks the homework as completed. + */ + public void markAsDone() { + status = Status.COMPLETED; + } + + /** + * Marks the homework as not completed. + */ + public void markAsUndone() { + status = Status.PENDING; + } + + /** + * Returns a tag to indicate whether the homework is completed or not. + * + * @return A tag to indicate whether the homework is completed or not. + */ + public String getStatusTag() { + return status == Status.COMPLETED ? COMPLETED_TAG : PENDING_TAG; + } + + /** + * Returns true if both homework have the same description. + * This defines a weaker notion of equality between two homework. + * + * @param otherHomework The other homework to compare with. + * @return True if both homework have the same description. + */ + public boolean isSameHomework(Homework otherHomework) { + if (otherHomework == this) { + return true; + } + + return otherHomework != null + && otherHomework.getDescription().equals(getDescription()); + } + + /** + * Returns a string representation of the homework. + * + * @return A string representation of the homework. + */ + public String getDeadlineString() { + return deadline.format(PRINT_FORMATTER); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Homework)) { + return false; + } + + Homework otherHomework = (Homework) other; + return otherHomework.getDescription().equals(getDescription()) + && otherHomework.getDeadline().equals(getDeadline()); + } + + @Override + public int hashCode() { + return Objects.hash(getDescription(), getDeadline()); + } + + @Override + public String toString() { + return String.format(TO_STRING_FORMAT, getStatusTag(), getDescription(), getDeadlineString()); + } +} diff --git a/src/main/java/seedu/address/model/student/HomeworkIsCompletePredicate.java b/src/main/java/seedu/address/model/student/HomeworkIsCompletePredicate.java new file mode 100644 index 00000000000..f434146c830 --- /dev/null +++ b/src/main/java/seedu/address/model/student/HomeworkIsCompletePredicate.java @@ -0,0 +1,37 @@ +package seedu.address.model.student; + +import java.util.function.Predicate; + +/** + * Tests that a {@code Homework}'s {@code isCompleted} matches the given boolean. + */ +public class HomeworkIsCompletePredicate implements Predicate { + private final boolean isComplete; + + /** + * Creates a predicate to test if a {@code Homework}'s {@code isCompleted} matches the given boolean. + * + * @param isComplete The boolean to test against. + */ + public HomeworkIsCompletePredicate(boolean isComplete) { + this.isComplete = isComplete; + } + + /** + * Tests if a {@code Homework}'s {@code isCompleted} matches the given boolean. + * + * @param homework The homework to test. + * @return True if the homework's isCompleted matches the given boolean. + */ + @Override + public boolean test(Homework homework) { + return homework.isCompleted() == isComplete; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof HomeworkIsCompletePredicate // instanceof handles nulls + && isComplete == ((HomeworkIsCompletePredicate) other).isComplete); // state check + } +} diff --git a/src/main/java/seedu/address/model/student/Lesson.java b/src/main/java/seedu/address/model/student/Lesson.java new file mode 100644 index 00000000000..18e5453da5d --- /dev/null +++ b/src/main/java/seedu/address/model/student/Lesson.java @@ -0,0 +1,91 @@ +package seedu.address.model.student; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Objects; + +/** + * Represents a Lesson in the address book. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Lesson { + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + private final String title; + private final LocalDateTime startTime; + private final LocalDateTime endTime; + + /** + * Every field must be present and not null. + */ + public Lesson(String title, LocalDateTime startTime, LocalDateTime endTime) { + Objects.requireNonNull(title); + Objects.requireNonNull(startTime); + Objects.requireNonNull(endTime); + + this.title = title; + this.startTime = startTime; + this.endTime = endTime; + } + + public String getTitle() { + return title; + } + + public LocalDateTime getStartTime() { + return startTime; + } + + public LocalDateTime getEndTime() { + return endTime; + } + + /** + * Returns true if the 2 lessons' timeslot conflict with each other. + * @param otherLesson The other lesson to compare with. + * @return True if the lessons' timeslots overlap each other. + */ + public boolean isSameTimeLesson(Lesson otherLesson) { + if (otherLesson == this) { + return true; + } + + return otherLesson != null + && otherLesson.getStartTime().isBefore(getEndTime()) && otherLesson.getEndTime().isAfter(getStartTime()); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Lesson)) { + return false; + } + + Lesson otherLesson = (Lesson) other; + return otherLesson.getTitle().equals(getTitle()) + && otherLesson.getStartTime().equals(getStartTime()) + && otherLesson.getEndTime().equals(getEndTime()); + } + + /** + * Returns true if the lesson has ended. + * + * @return True if the lesson has ended. + */ + public boolean isPastLesson() { + return getEndTime().isBefore(LocalDateTime.now()); + } + + @Override + public int hashCode() { + return Objects.hash(getTitle(), getStartTime(), getEndTime()); + } + + @Override + public String toString() { + return String.format("%s, starts: %s, ends: %s", title, + startTime.format(formatter), endTime.format(formatter)); + } +} diff --git a/src/main/java/seedu/address/model/student/LessonBelongsToDatePredicate.java b/src/main/java/seedu/address/model/student/LessonBelongsToDatePredicate.java new file mode 100644 index 00000000000..e94e51db3f0 --- /dev/null +++ b/src/main/java/seedu/address/model/student/LessonBelongsToDatePredicate.java @@ -0,0 +1,38 @@ +package seedu.address.model.student; + +import java.time.LocalDate; +import java.util.function.Predicate; + +/** + * Tests that a {@code Lesson}'s date matches the given date. + */ +public class LessonBelongsToDatePredicate implements Predicate { + private final LocalDate targetDate; + + /** + * Creates a LessonBelongsToDatePredicate to test if a {@code Lesson}'s date matches the given date. + * + * @param targetDate The date to test against. + */ + public LessonBelongsToDatePredicate(LocalDate targetDate) { + this.targetDate = targetDate; + } + + /** + * Tests if a {@code Lesson}'s date matches the given date. + * + * @param lesson The lesson to test. + * @return True if the lesson's date matches the given date. + */ + @Override + public boolean test(Lesson lesson) { + return lesson.getStartTime().toLocalDate().isEqual(targetDate); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof LessonBelongsToDatePredicate // instanceof handles nulls + && targetDate.isEqual(((LessonBelongsToDatePredicate) other).targetDate)); // date check + } +} diff --git a/src/main/java/seedu/address/model/student/LessonDonePredicate.java b/src/main/java/seedu/address/model/student/LessonDonePredicate.java new file mode 100644 index 00000000000..4e9c718d882 --- /dev/null +++ b/src/main/java/seedu/address/model/student/LessonDonePredicate.java @@ -0,0 +1,41 @@ +package seedu.address.model.student; + +import java.time.LocalDateTime; +import java.util.function.Predicate; + +/** + * Tests that a {@code Lesson}'s title matches the given done predicate. + */ +public class LessonDonePredicate implements Predicate { + private final String done; + + /** + * Creates a LessonDonePredicate to test if a {@code Lesson}'s title matches the given done predicate. + * + * @param done The done predicate to test against. + */ + public LessonDonePredicate(String done) { + this.done = done; + } + + /** + * Tests if a {@code Lesson}'s title matches the given done predicate. + * + * @param lesson The lesson to test. + * @return True if the lesson's title matches the given done predicate. + */ + @Override + public boolean test(Lesson lesson) { + if (done.equals("done")) { + return lesson.getStartTime().isBefore(LocalDateTime.now()); + } + return lesson.getStartTime().isAfter((LocalDateTime.now())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof LessonDonePredicate // instanceof handles nulls + && done.equals(((LessonDonePredicate) other).done)); // date check + } +} diff --git a/src/main/java/seedu/address/model/student/LessonSubjectPredicate.java b/src/main/java/seedu/address/model/student/LessonSubjectPredicate.java new file mode 100644 index 00000000000..e56ca3a74e5 --- /dev/null +++ b/src/main/java/seedu/address/model/student/LessonSubjectPredicate.java @@ -0,0 +1,37 @@ +package seedu.address.model.student; + +import java.util.function.Predicate; + +/** + * Tests that a {@code Lesson}'s title matches the given subject. + */ +public class LessonSubjectPredicate implements Predicate { + private final String subject; + + /** + * Creates a LessonSubjectPredicate to test if a {@code Lesson}'s title matches the given subject. + * + * @param subject The subject to test against. + */ + public LessonSubjectPredicate(String subject) { + this.subject = subject; + } + + /** + * Tests if a {@code Lesson}'s title matches the given subject. + * + * @param lesson The lesson to test. + * @return True if the lesson's title matches the given subject. + */ + @Override + public boolean test(Lesson lesson) { + return lesson.getTitle().contains(subject); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof LessonSubjectPredicate // instanceof handles nulls + && subject.equals(((LessonSubjectPredicate) other).subject)); // date check + } +} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/student/Name.java similarity index 81% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/seedu/address/model/student/Name.java index 79244d71cf7..ac415293966 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/student/Name.java @@ -1,10 +1,10 @@ -package seedu.address.model.person; +package seedu.address.model.student; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's name in the address book. + * Represents a Student's name in the address book. * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} */ public class Name { @@ -33,11 +33,21 @@ public Name(String name) { /** * Returns true if a given string is a valid name. + * + * @param test String to be tested for validity as a name. */ public static boolean isValidName(String test) { return test.matches(VALIDATION_REGEX); } + /** + * Returns the first name of the student. + * + * @return First name of the student. + */ + public String getFirstName() { + return fullName.split(" ")[0]; + } @Override public String toString() { @@ -55,5 +65,4 @@ public boolean equals(Object other) { public int hashCode() { return fullName.hashCode(); } - } diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/student/NameContainsKeywordsPredicate.java similarity index 90% rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java rename to src/main/java/seedu/address/model/student/NameContainsKeywordsPredicate.java index c9b5868427c..469956c74bb 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/student/NameContainsKeywordsPredicate.java @@ -1,4 +1,4 @@ -package seedu.address.model.person; +package seedu.address.model.student; import java.util.List; import java.util.function.Predicate; @@ -8,7 +8,7 @@ /** * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. */ -public class NameContainsKeywordsPredicate implements Predicate { +public class NameContainsKeywordsPredicate implements Predicate { private final List keywords; public NameContainsKeywordsPredicate(List keywords) { @@ -16,7 +16,7 @@ public NameContainsKeywordsPredicate(List keywords) { } @Override - public boolean test(Person person) { + public boolean test(Student person) { return keywords.stream() .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); } @@ -27,5 +27,4 @@ public boolean equals(Object other) { || (other instanceof NameContainsKeywordsPredicate // instanceof handles nulls && keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check } - } diff --git a/src/main/java/seedu/address/model/student/NamePredicate.java b/src/main/java/seedu/address/model/student/NamePredicate.java new file mode 100644 index 00000000000..f9fdfa3ef96 --- /dev/null +++ b/src/main/java/seedu/address/model/student/NamePredicate.java @@ -0,0 +1,42 @@ +package seedu.address.model.student; + +import java.util.List; +import java.util.function.Predicate; + +/** + * Tests that a {@code Student}'s name matches the given name. + */ +public class NamePredicate implements Predicate { + private final List names; + + /** + * Creates a NamePredicate to test if a {@code Student}'s name matches the given name. + * + * @param names The name to test against. + */ + public NamePredicate(List names) { + this.names = names; + } + + /** + * Tests if a {@code Student}'s name matches the given name. + * + * @param student The student to test. + * @return True if the student's name matches the given name. + */ + @Override + public boolean test(Student student) { + for (String name : names) { + if (student.getName().toString().toLowerCase().contains(name.toLowerCase())) { + return true; + } + } + return false; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof NamePredicate && names.equals(((NamePredicate) other).names)); + } +} diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/student/Phone.java similarity index 97% rename from src/main/java/seedu/address/model/person/Phone.java rename to src/main/java/seedu/address/model/student/Phone.java index 872c76b382f..2ccc9aa4c72 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/address/model/student/Phone.java @@ -1,4 +1,4 @@ -package seedu.address.model.person; +package seedu.address.model.student; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; diff --git a/src/main/java/seedu/address/model/student/Student.java b/src/main/java/seedu/address/model/student/Student.java new file mode 100644 index 00000000000..fa5e01859cf --- /dev/null +++ b/src/main/java/seedu/address/model/student/Student.java @@ -0,0 +1,532 @@ +package seedu.address.model.student; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.Predicate; + +import javafx.collections.ObservableList; +import javafx.scene.chart.PieChart; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.model.student.exceptions.ConflictingExamsException; +import seedu.address.model.student.exceptions.ConflictingLessonsException; +import seedu.address.model.student.exceptions.DuplicateEntryException; +import seedu.address.model.tag.Tag; + +/** + * Represents a Person in the address book. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Student { + // Identity fields + private final Name name; + private final Phone phone; + private final Email email; + + // Data fields + private final Address address; + private final Set tags = new HashSet<>(); + private final UniqueHomeworkList homeworkList = new UniqueHomeworkList(); + private final UniqueLessonsList lessonsList = new UniqueLessonsList(); + private final UniqueExamList examList = new UniqueExamList(); + + /** + * An overloaded constructor for Student class. + * where homeworkList, lessonList and examList are empty. + * + * @param name name of student. + * @param phone phone number of student. + * @param email email of student. + * @param address address of student. + * @param tags tags of student. + */ + public Student(Name name, Phone phone, Email email, Address address, Set tags) { + requireAllNonNull(name, phone, email, address, tags); + this.name = name; + this.phone = phone; + this.email = email; + this.address = address; + this.tags.addAll(tags); + } + + /** + * An overloaded constructor for Student class. + * Every field must be present and not null. + * + * @param name name of student. + * @param phone phone number of student. + * @param email email of student. + * @param address address of student. + * @param tags tags of student. + * @param homeworkList list of homework of student. + * @param lessonList list of lessons of student. + * @param examList list of exams of student. + */ + public Student(Name name, Phone phone, Email email, Address address, Set tags, List homeworkList, + List lessonList, List examList) { + requireAllNonNull(name, phone, email, address, tags, homeworkList, lessonList); + this.name = name; + this.phone = phone; + this.email = email; + this.address = address; + this.tags.addAll(tags); + this.homeworkList.setHomeworks(homeworkList); + this.lessonsList.setLessons(lessonList); + this.examList.setExams(examList); + } + + public Name getName() { + return name; + } + + public Phone getPhone() { + return phone; + } + + public Email getEmail() { + return email; + } + + public Address getAddress() { + return address; + } + + /** + * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * + * @return set of tags + */ + public Set getTags() { + return Collections.unmodifiableSet(tags); + } + + /** + * Returns an immutable assignment list, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * + * @return list of homework + */ + public ObservableList getHomeworkList() { + return homeworkList.asUnmodifiableObservableList(); + } + + /** + * Returns an immutable lessons list, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * + * @return list of lessons + */ + public ObservableList getLessonsList() { + return lessonsList.asUnmodifiableObservableList(); + } + + /** + * Returns an immutable exam list, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * + * @return list of exams + */ + public ObservableList getExamsList() { + return examList.asUnmodifiableObservableList(); + } + + //HOMEWORK######################################################################################## + /** + * Adds a homework to the homework list. + * + * @param homework homework to be added + */ + public void addHomework(Homework homework) throws DuplicateEntryException { + // check for duplicate homework + if (homeworkList.contains(homework)) { + throw new DuplicateEntryException(); + } + + this.homeworkList.addHomework(homework); + } + + /** + * Returns a homework from the homework list. + * + * @param index index of homework to be returned + * @return homework + */ + public Homework getHomework(Index index) { + return this.homeworkList.getHomework(index.getZeroBased()); + } + + /** + * Deletes a homework from the homework list. + * + * @param index index of homework to be deleted + */ + public void deleteHomework(Index index) { + Homework homeworkToDelete = this.homeworkList.getHomework(index.getZeroBased()); + this.homeworkList.removeHomework(homeworkToDelete); + } + + /** + * Updates a homework from the homework list. + * + * @param target homework to be updated + * @param editedHomework homework to be updated to + */ + public void setHomework(Homework target, Homework editedHomework) { + requireAllNonNull(target, editedHomework); + homeworkList.setHomework(target, editedHomework); + } + + /** + * Marks a homework as done from the homework list. + * + * @param index index of homework to be marked as done + */ + public void markHomeworkAsDone(Index index) { + Homework homeworkToMarkAsDone = this.homeworkList.getHomework(index.getZeroBased()); + homeworkToMarkAsDone.markAsDone(); + } + + /** + * Marks a homework as undone from the homework list. + * + * @param index index of homework to be marked as undone + */ + public void markHomeworkAsUndone(Index index) { + Homework homeworkToMarkAsUndone = this.homeworkList.getHomework(index.getZeroBased()); + homeworkToMarkAsUndone.markAsUndone(); + } + + /** + * Returns an immutable assignment list, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * + * @return list of filtered homework + */ + public ObservableList getFilteredHomeworkList(Predicate predicate) { + return homeworkList.getFilteredHomeworkList(predicate); + } + + /** + * Returns the pie chart data for the homework list. + * + * @return pie chart data for the homework list + */ + public ObservableList getHomeworkPieChartData() { + return homeworkList.getHomeworkPieChartData(); + } + + //LESSONS######################################################################################## + /** + * Returns a homework from the homework list. + * + * @param index index of homework to be returned + * @return homework + */ + public Lesson getLesson(Index index) { + return this.lessonsList.getLesson(index.getZeroBased()); + } + + /** + * Deletes a homework from the homework list. + * + * @param index index of homework to be deleted + */ + public void deleteLesson(Index index) { + Lesson lessonToDelete = this.lessonsList.getLesson(index.getZeroBased()); + this.lessonsList.remove(lessonToDelete); + } + /** + * Adds a lesson to the lesson list + * @param lesson the lesson to be added + */ + public void addLesson(Lesson lesson) throws ConflictingLessonsException { + if (hasExamAtSameTime(lesson)) { + throw new ConflictingLessonsException(Messages.MESSAGE_CONFLICTING_EXAM_TIME); + } + try { + this.lessonsList.add(lesson); + } catch (Exception e) { + throw new ConflictingLessonsException(e.getMessage()); + } + } + + private boolean hasExamAtSameTime(Lesson lesson) { + for (Exam exam : examList) { + if (lesson.getStartTime().isBefore(exam.getEndTime()) && lesson.getEndTime().isAfter(exam.getStartTime())) { + return true; + } + } + return false; + } + + /** + * Returns an immutable assignment list, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * + * @return list of filtered homework + */ + @SafeVarargs + public final List getFilteredLessonsList(Predicate... predicates) { + List filteredLessonsList = new ArrayList<>(); + + for (Lesson l : lessonsList) { + boolean allPredicatesMatch = true; + for (Predicate predicate : predicates) { + if (!predicate.test(l)) { + allPredicatesMatch = false; + break; + } + } + if (allPredicatesMatch) { + filteredLessonsList.add(l); + } + } + + return Collections.unmodifiableList(filteredLessonsList); + } + + public void setLesson(Integer target, Lesson editedLesson) { + requireAllNonNull(target, editedLesson); + lessonsList.setLesson(target, editedLesson); + } + + public void setExam(Integer target, Exam editedExam) { + requireAllNonNull(target, editedExam); + examList.setExam(target, editedExam); + } + + /** + * Returns an immutable assignment list, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * + * @return list of filtered homework + */ + public ObservableList getPastLessonsList() { + return lessonsList.getPastLessons(); + } + + /** + * Returns an immutable assignment list, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * + * @return list of filtered homework + */ + public ObservableList getUpcomingLessonsList() { + return lessonsList.getUpcomingLessons(); + } + + /** + * Returns an immutable assignment list, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * + * @return list of filtered homework + */ + public boolean hasLesson() { + return this.lessonsList.hasLesson(); + } + + //Exams######################################################################################## + + /** + * Returns an immutable assignment list, which throws {@code UnsupportedOperationException} + * + * @return list of pending homework + */ + public List getUpcomingExamList() { + List upcomingExamList = new ArrayList<>(); + + // filter exam list for pending exams + for (Exam exam : examList) { + if (exam.getStartTime().isAfter(LocalDateTime.now())) { + upcomingExamList.add(exam); + } + } + + return Collections.unmodifiableList(upcomingExamList); + } + + /** + * Returns an immutable exam list, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * + * @return list of filtered exams + */ + public List getFilteredExamList(Predicate... predicates) { + List filteredExamList = new ArrayList<>(); + + for (Exam e : examList) { + boolean allPredicatesMatch = true; + for (Predicate predicate : predicates) { + if (!predicate.test(e)) { + allPredicatesMatch = false; + break; + } + } + if (allPredicatesMatch) { + filteredExamList.add(e); + } + } + + return Collections.unmodifiableList(filteredExamList); + } + + /** + * Adds an Exam to the exam list. + * + * @param exam Exam to be added + * @throws DuplicateEntryException when exam already exists + */ + public void addExam(Exam exam) throws DuplicateEntryException, ConflictingExamsException { + // check for duplicate homework + if (examList.contains(exam)) { + throw new DuplicateEntryException(); + } + this.examList.add(exam); + } + + /** + * Returns true if the exam's time clashes with any lesson's time. + * @param exam Exam to be checked + * @return boolean + */ + public boolean hasLessonAtSameTime(Exam exam) { + for (Lesson lesson : lessonsList) { + if (exam.getStartTime().isBefore(lesson.getEndTime()) && exam.getEndTime().isAfter(lesson.getStartTime())) { + return true; + } + } + return false; + } + + /** + * Returns an exam from the exam list. + * + * @param index index of exam to be returned + * @return Exam + */ + public Exam getExam(Index index) { + return this.examList.getExam(index.getZeroBased()); + } + + /** + * Deletes an exam from the exam list. + * + * @param index index of exam to be deleted + */ + public void deleteExam(Index index) { + Exam examToDelete = this.examList.getExam(index.getZeroBased()); + this.examList.remove(examToDelete); + } + + //UTIL######################################################################################## + /** + * Returns true if both persons have the same name. + * This defines a weaker notion of equality between two persons. + * + * @param otherPerson other person to compare to + * @return boolean + */ + public boolean isSameStudent(Student otherPerson) { + if (otherPerson == this) { + return true; + } + + return otherPerson != null + && otherPerson.getName().equals(getName()); + } + + /** + * Returns true if both persons have the same identity and data fields. + * This defines a stronger notion of equality between two persons. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Student)) { + return false; + } + + Student otherPerson = (Student) other; + return otherPerson.getName().equals(getName()) + && otherPerson.getPhone().equals(getPhone()) + && otherPerson.getEmail().equals(getEmail()) + && otherPerson.getAddress().equals(getAddress()) + && otherPerson.getTags().equals(getTags()) + && otherPerson.getHomeworkList().equals(getHomeworkList()) + && otherPerson.getLessonsList().equals(getLessonsList()); + } + + /** + * Returns a hash code for the person. + * + * @return hash code + */ + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(name, phone, email, address, tags, homeworkList, lessonsList); + } + + /** + * Returns a string representation of the person. + * Format: name; phone; email; address; tags; assignments + * + * @return string representation of the person + */ + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getName()) + .append("\nPhone: ") + .append(getPhone()) + .append("\n") + .append("Email: ") + .append(getEmail()) + .append("\n") + .append("Address: ") + .append(getAddress()) + .append("\n") + .append("Tags: "); + getTags().forEach(builder::append); + return builder.toString(); + } + + /** + * Returns an immutable assignment list, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * + * @return list of filtered homework + */ + public ObservableList getPastExamsList() { + return examList.getPastExams(); + } + + /** + * Returns an immutable assignment list, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * + * @return list of filtered homework + */ + public ObservableList getUpcomingExamsList() { + return examList.getUpcomingExams(); + } + + public boolean hasConflictingLessonTime(Lesson lesson) { + return this.lessonsList.hasConflictingLessonTime(lesson); + } + + public boolean hasConflictingExamTime(Lesson lesson) { + return this.examList.hasConflictingExamTime(lesson); + } +} diff --git a/src/main/java/seedu/address/model/student/UniqueExamList.java b/src/main/java/seedu/address/model/student/UniqueExamList.java new file mode 100644 index 00000000000..a2bb3e4e674 --- /dev/null +++ b/src/main/java/seedu/address/model/student/UniqueExamList.java @@ -0,0 +1,238 @@ +package seedu.address.model.student; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.student.exceptions.ConflictingExamsException; +import seedu.address.model.student.exceptions.DuplicateEntryException; +import seedu.address.model.student.exceptions.DuplicateExamsException; +import seedu.address.model.student.exceptions.EntryNotFoundException; + +/** + * A list of exams that enforces uniqueness between its elements and does not allow nulls. + * An exam is considered unique by comparing using {@code Exam#isSameExam(Exam)}. + * As such, adding and updating of exam uses Exam#isSameExam(Exam) for equality + * so as to ensure that the Exam being added or updated is unique in terms of identity in the + * UniqueExamList. However, the removal of a homework uses Exam#equals(Object) so as to ensure + * that the Exam with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + */ +public class UniqueExamList implements Iterable { + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent Exam as the given argument. + * + * @param toCheck the Exam to be checked + * @return true if the list contains the Exam + */ + public boolean contains(Exam toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameExam); + } + + /** + * Adds an Exam to the list. + * The Exam must not already exist in the list. + * + * @param toAdd the Exam to be added + */ + public void add(Exam toAdd) throws DuplicateExamsException, ConflictingExamsException { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateExamsException(); + } + for (Exam exam : internalList) { + if (exam.isSameTimeExam(toAdd)) { + throw new ConflictingExamsException(); + } + } + internalList.add(toAdd); + internalList.sort((l1, l2) -> l1.getStartTime().compareTo(l2.getStartTime())); + } + + /** + * Replaces the Exam {@code target} in the list with {@code editedExam}. + * {@code target} must exist in the list. + * The Exam identity of {@code editedHomework} must not be the same as another existing homework in the list. + */ + public void setExam(Integer target, Exam editedExam) { + requireAllNonNull(target, editedExam); + if (contains(editedExam)) { + throw new DuplicateEntryException(); + } + Exam originalExam = internalList.get(target); + internalList.set(target, editedExam); + if (!this.validExams()) { + internalList.set(target, originalExam); + throw new ConflictingExamsException(); + } + } + + /** + * Removes the equivalent Exam from the list. + * The Exam must exist in the list. + * + * @param toRemove the Exam to be removed + */ + public void remove(Exam toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new EntryNotFoundException(); + } + } + + /** + * Replaces the contents of this list with {@code replacement}. + * {@code replacement} must not contain duplicate Exams. + * + * @param replacement the homeworks to be added to the list + */ + public void setExams(UniqueExamList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Overloaded method that accepts a List of Exams item + * Replaces the contents of this list with {@code replacement}. + * {@code replacement} must not contain duplicate Exams. + * + * @param replacement the Exams to be added to the list + */ + public void setExams(List replacement) { + requireAllNonNull(replacement); + if (!examsAreUnique(replacement)) { + throw new DuplicateEntryException(); + } + + internalList.setAll(replacement); + } + + /** + * Returns the exam object of index 'index'. + * + * @param index of the Exam to be returned + * @return the Exam at that index in the exam list. + */ + public Exam getExam(int index) { + return internalList.get(index); + } + + /** + * Returns the Exam list as an unmodifiable {@code ObservableList}. + * + * @return the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + /** + * Returns an iterator over the elements in this list in proper sequence. + * + * @return the iterator over the Exam objects in this list. + */ + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + /** + * Returns true if {@code exams} contains only unique Exams. + * + * @param exams a list of exams to be checked + * @return true if {@code exams} contains only unique homeworks + */ + private boolean examsAreUnique(List exams) { + for (int i = 0; i < exams.size() - 1; i++) { + for (int j = i + 1; j < exams.size(); j++) { + if (exams.get(i).isSameExam(exams.get(j))) { + return false; + } + } + } + return true; + } + + private boolean examsAreCompatible(List exams) { + for (int i = 0; i < exams.size() - 1; i++) { + for (int j = i + 1; j < exams.size(); j++) { + if (exams.get(i).isSameTimeExam(exams.get(j))) { + return false; + } + } + } + return true; + } + + public boolean validExams() { + return examsAreUnique(internalList) && examsAreCompatible(internalList); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueExamList // instanceof handles nulls + && internalList.equals(((UniqueExamList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns a list of past exams. + * The list is sorted by start time and capped at 3 exams. + * + * @return a list of past exams. + */ + public ObservableList getPastExams() { + ObservableList pastExams = FXCollections.observableArrayList(); + for (Exam exam : internalList) { + if (exam.isPastExam()) { + pastExams.add(exam); + } + } + return pastExams; + } + + /** + * Returns a list of upcoming exams. + * The list is sorted by start time and capped at 3 exams. + * + * @return a list of upcoming exams. + */ + public ObservableList getUpcomingExams() { + List upcomingExams = FXCollections.observableArrayList(); + for (Exam exam : internalList) { + if (!exam.isPastExam()) { + upcomingExams.add(exam); + } + } + return FXCollections.observableArrayList(upcomingExams); + } + + /** + * Returns true if the list contains an exam with the same time as the lesson. + * @param lesson the lesson to be checked + * @return true if the list contains an exam with the same time as the lesson. + */ + public boolean hasConflictingExamTime(Lesson lesson) { + for (Exam exam : internalList) { + if (exam.isSameTimeLesson(lesson)) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/seedu/address/model/student/UniqueHomeworkList.java b/src/main/java/seedu/address/model/student/UniqueHomeworkList.java new file mode 100644 index 00000000000..ee3feea0272 --- /dev/null +++ b/src/main/java/seedu/address/model/student/UniqueHomeworkList.java @@ -0,0 +1,200 @@ +package seedu.address.model.student; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.function.Predicate; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.chart.PieChart; +import seedu.address.model.student.exceptions.DuplicateEntryException; +import seedu.address.model.student.exceptions.EntryNotFoundException; + +/** + * A list of homework that enforces uniqueness between its elements and does not allow nulls. + * A homework is considered unique by comparing using {@code Homework#isSameHomework(Homework)}. + * As such, adding and updating of homework uses Homework#isSameHomework(Homework) for equality + * to ensure that the homework being added or updated is unique in terms of identity in the + * UniqueHomeworkList. However, the removal of a homework uses Homework#equals(Object) so as to ensure + * that the homework with exactly the same fields will be removed. + */ +public class UniqueHomeworkList implements Iterable { + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent homework as the given argument. + * + * @param toCheck the homework to be checked + * @return true if the list contains the homework + */ + public boolean contains(Homework toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameHomework); + } + + /** + * Adds a homework to the list. + * The homework must not already exist in the list. + * + * @param toAdd the homework to be added + */ + public void addHomework(Homework toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateEntryException(); + } + internalList.add(toAdd); + internalList.sort(Comparator.comparing(Homework::getDeadline)); + } + + /** + * Replaces the homework {@code target} in the list with {@code editedHomework}. + * {@code target} must exist in the list. + * The homework identity of {@code editedHomework} must not be the same as another existing homework in the list. + */ + public void setHomework(Homework target, Homework editedHomework) { + requireAllNonNull(target, editedHomework); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new EntryNotFoundException(); + } + + if (!target.isSameHomework(editedHomework) && contains(editedHomework)) { + throw new DuplicateEntryException(); + } + + internalList.set(index, editedHomework); + } + + /** + * Removes the equivalent homework from the list. + * The homework must exist in the list. + * + * @param toRemove the homework to be removed + */ + public void removeHomework(Homework toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new EntryNotFoundException(); + } + } + + /** + * Replaces the contents of this list with {@code homeworks}. + * {@code homeworks} must not contain duplicate homeworks. + * + * @param homeworks the homeworks to be added to the list + */ + public void setHomeworks(List homeworks) { + requireAllNonNull(homeworks); + if (!homeworksAreUnique(homeworks)) { + throw new DuplicateEntryException(); + } + + internalList.setAll(homeworks); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + * + * @param index of the homework to be returned + * @return the backing list as an unmodifiable {@code ObservableList}. + */ + public Homework getHomework(int index) { + return internalList.get(index); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + * + * @return the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + * + * @return the backing list as an unmodifiable {@code ObservableList}. + */ + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + /** + * Returns true if {@code homeworks} contains only unique homeworks. + * + * @param homeworks the homeworks to be checked + * @return true if {@code homeworks} contains only unique homeworks + */ + private boolean homeworksAreUnique(List homeworks) { + for (int i = 0; i < homeworks.size() - 1; i++) { + for (int j = i + 1; j < homeworks.size(); j++) { + if (homeworks.get(i).isSameHomework(homeworks.get(j))) { + return false; + } + } + } + return true; + } + + /** + * Gets the filtered homework list. + * + * @param homeworkStatusPredicate the predicate to filter the homework list + * @return the filtered homework list + */ + public ObservableList getFilteredHomeworkList(Predicate homeworkStatusPredicate) { + requireNonNull(homeworkStatusPredicate); + ObservableList filteredHomeworkList = FXCollections.observableArrayList(); + for (Homework homework : internalList) { + if (homeworkStatusPredicate.test(homework)) { + filteredHomeworkList.add(homework); + } + } + return filteredHomeworkList; + } + + /** + * Returns the pie chart data for the homework list. + * + * @return the pie chart data for the homework list + */ + public ObservableList getHomeworkPieChartData() { + ObservableList pieChartData = FXCollections.observableArrayList(); + int completedHomework = 0; + int uncompletedHomework = 0; + for (Homework homework : internalList) { + if (homework.isCompleted()) { + completedHomework++; + } else { + uncompletedHomework++; + } + } + pieChartData.add(new PieChart.Data("Completed", completedHomework)); + pieChartData.add(new PieChart.Data("Pending", uncompletedHomework)); + return pieChartData; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueHomeworkList // instanceof handles nulls + && internalList.equals(((UniqueHomeworkList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/student/UniqueLessonsList.java b/src/main/java/seedu/address/model/student/UniqueLessonsList.java new file mode 100644 index 00000000000..1adce8594ed --- /dev/null +++ b/src/main/java/seedu/address/model/student/UniqueLessonsList.java @@ -0,0 +1,222 @@ +package seedu.address.model.student; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.student.exceptions.ConflictingLessonsException; +import seedu.address.model.student.exceptions.DuplicateEntryException; +import seedu.address.model.student.exceptions.DuplicateLessonException; +import seedu.address.model.student.exceptions.LessonNotFoundException; + +/** + * A list of lessons that enforces uniqueness between its elements and does not allow nulls. + * A lesson is considered unique by comparing using {@code Lesson#isSameLesson(Lesson)}. + * As such, adding and updating of lessons uses Lesson#isSameLesson(Lesson) for equality so as to ensure + * that the lesson being added or updated is unique in terms of identity in the UniqueLessonList. + * However, the removal of a lesson uses Lesson#equals(Object) so as to ensure that the lesson with + * exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Lesson#isSameLesson(Lesson) + */ +public class UniqueLessonsList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains a lesson that has time conflict as the given argument. + * + * @param toCheck the lesson to be checked + * @return true if the list contains a lesson with conflicting timeslot + */ + public boolean contains(Lesson toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::equals); + } + + /** + * Adds a lesson to the list. + * The lesson must not already exist in the list. + * + * @param toAdd the lesson to be added + */ + public void add(Lesson toAdd) throws DuplicateLessonException, ConflictingLessonsException { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateLessonException(); + } + for (Lesson lesson : internalList) { + if (lesson.isSameTimeLesson(toAdd)) { + throw new ConflictingLessonsException(); + } + } + internalList.add(toAdd); + internalList.sort(Comparator.comparing(Lesson::getStartTime)); + } + + /** + * Removes the equivalent lesson from the list. + * The lesson must exist in the list. + * + * @param toRemove the homework to be removed + */ + public void remove(Lesson toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new LessonNotFoundException(); + } + internalList.sort(Comparator.comparing(Lesson::getStartTime)); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + * + * @param index of the lesson to be returned + * @return the backing list as an unmodifiable {@code ObservableList}. + */ + public Lesson getLesson(int index) { + return internalList.get(index); + } + + /** + * Replaces the contents of this list with {@code lessons}. + * {@code lessons} must not contain conflicting lessons. + * + * @param lessons the lessons to be added to the list + */ + public void setLessons(List lessons) { + requireAllNonNull(lessons); + if (!lessonsAreCompatible(lessons)) { + throw new ConflictingLessonsException(); + } + internalList.setAll(lessons); + internalList.sort(Comparator.comparing(Lesson::getStartTime)); + } + + public boolean validLessons() { + return lessonsAreCompatible(internalList); + } + + public void setLesson(Integer target, Lesson editedLesson) { + requireAllNonNull(target, editedLesson); + if (target >= internalList.size()) { + throw new LessonNotFoundException(); + } + if (contains(editedLesson)) { + throw new DuplicateEntryException(); + } + Lesson originalLesson = internalList.get(target); + internalList.set(target, editedLesson); + if (!this.validLessons()) { + internalList.set(target, originalLesson); + throw new ConflictingLessonsException(); + } + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + * + * @return the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + * + * @return the backing list as an unmodifiable {@code ObservableList}. + */ + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + /** + * Returns true if no lessons in {@code lessons} have conflicting time slots + * + * @param lessons the lessons to be checked. + * @return true if {@code lessons} contains only lessons with no conflicting timeslots. + */ + private boolean lessonsAreCompatible(List lessons) { + for (int i = 0; i < lessons.size() - 1; i++) { + for (int j = i + 1; j < lessons.size(); j++) { + if (lessons.get(i).isSameTimeLesson(lessons.get(j))) { + return false; + } + } + } + return true; + } + + /** + * Checks the number of lessons in the list + * @return an int that is the number of lessons in the list + */ + public int size() { + return this.internalList.size(); + } + + public List getSortedLessonsList() { + List tempList = internalList; + tempList.sort(Comparator.comparing(Lesson::getStartTime)); + return tempList; + } + + /** + * Returns a list of lessons that are upcoming. + * + * @return a list of lessons that are upcoming. + */ + public ObservableList getPastLessons() { + ObservableList pastLessons = FXCollections.observableArrayList(); + for (Lesson lesson : internalList) { + if (lesson.isPastLesson()) { + pastLessons.add(lesson); + } + } + return pastLessons; + } + + /** + * Returns a list of lessons that are upcoming. + * + * @return a list of lessons that are upcoming. + */ + public ObservableList getUpcomingLessons() { + ObservableList upcomingLessons = FXCollections.observableArrayList(); + for (Lesson lesson : internalList) { + if (!lesson.isPastLesson()) { + upcomingLessons.add(lesson); + } + } + return upcomingLessons; + } + + public boolean hasLesson() { + return this.internalList.size() != 0; + } + + /** + * Returns true if the list contains a lesson that has time conflict as the given argument. + * @param lesson the lesson to be checked + * @return true if the list contains a lesson with conflicting timeslot + */ + public boolean hasConflictingLessonTime(Lesson lesson) { + for (Lesson l : internalList) { + if (l.isSameTimeLesson(lesson)) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/seedu/address/model/student/UniqueStudentList.java b/src/main/java/seedu/address/model/student/UniqueStudentList.java new file mode 100644 index 00000000000..1956da3de59 --- /dev/null +++ b/src/main/java/seedu/address/model/student/UniqueStudentList.java @@ -0,0 +1,137 @@ +package seedu.address.model.student; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.student.exceptions.DuplicateStudentException; +import seedu.address.model.student.exceptions.StudentNotFoundException; + +/** + * A list of persons that enforces uniqueness between its elements and does not allow nulls. + * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, adding and updating of + * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is + * unique in terms of identity in the UniqueStudentList. However, the removal of a person uses Person#equals(Object) so + * as to ensure that the person with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Student#isSameStudent(Student) + */ +public class UniqueStudentList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent person as the given argument. + */ + public boolean contains(Student toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameStudent); + } + + /** + * Adds a person to the list. + * The person must not already exist in the list. + */ + public void add(Student toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateStudentException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the person {@code target} in the list with {@code editedStudent}. + * {@code target} must exist in the list. + * The person identity of {@code editedStudent} must not be the same as another existing person in the list. + */ + public void setPerson(Student target, Student editedStudent) { + requireAllNonNull(target, editedStudent); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new StudentNotFoundException(); + } + + if (!target.isSameStudent(editedStudent) && contains(editedStudent)) { + throw new DuplicateStudentException(); + } + + internalList.set(index, editedStudent); + } + + /** + * Removes the equivalent person from the list. + * The person must exist in the list. + */ + public void remove(Student toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new StudentNotFoundException(); + } + } + + public void setStudents(UniqueStudentList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code students}. + * {@code students} must not contain duplicate students. + */ + public void setStudents(List students) { + requireAllNonNull(students); + if (!studentsAreUnique(students)) { + throw new DuplicateStudentException(); + } + + internalList.setAll(students); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueStudentList // instanceof handles nulls + && internalList.equals(((UniqueStudentList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code students} contains only unique students. + */ + private boolean studentsAreUnique(List students) { + for (int i = 0; i < students.size() - 1; i++) { + for (int j = i + 1; j < students.size(); j++) { + if (students.get(i).isSameStudent(students.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/student/exceptions/ConflictingExamsException.java b/src/main/java/seedu/address/model/student/exceptions/ConflictingExamsException.java new file mode 100644 index 00000000000..738309361e5 --- /dev/null +++ b/src/main/java/seedu/address/model/student/exceptions/ConflictingExamsException.java @@ -0,0 +1,11 @@ +package seedu.address.model.student.exceptions; + +/** + * Signals that the operation will result in duplicate Exams (Exams are considered duplicates if they have the same + * description, start time, end time, weightage, status, and grade). + */ +public class ConflictingExamsException extends RuntimeException { + public ConflictingExamsException() { + super("The exam clashes with another exam/lesson"); + } +} diff --git a/src/main/java/seedu/address/model/student/exceptions/ConflictingLessonsException.java b/src/main/java/seedu/address/model/student/exceptions/ConflictingLessonsException.java new file mode 100644 index 00000000000..3ac6c428997 --- /dev/null +++ b/src/main/java/seedu/address/model/student/exceptions/ConflictingLessonsException.java @@ -0,0 +1,13 @@ +package seedu.address.model.student.exceptions; + +/** + * Signals that the operation will result in lessons with conflicting timeslots + */ +public class ConflictingLessonsException extends RuntimeException { + public ConflictingLessonsException() { + super("Operation would result in lessons with overlapping timeslots"); + } + public ConflictingLessonsException(String message) { + super(message); + } +} diff --git a/src/main/java/seedu/address/model/student/exceptions/DuplicateEntryException.java b/src/main/java/seedu/address/model/student/exceptions/DuplicateEntryException.java new file mode 100644 index 00000000000..f27a42f3844 --- /dev/null +++ b/src/main/java/seedu/address/model/student/exceptions/DuplicateEntryException.java @@ -0,0 +1,11 @@ +package seedu.address.model.student.exceptions; + +/** + * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same + * identity). + */ +public class DuplicateEntryException extends RuntimeException { + public DuplicateEntryException() { + super("Operation would result in duplicate entries"); + } +} diff --git a/src/main/java/seedu/address/model/student/exceptions/DuplicateExamsException.java b/src/main/java/seedu/address/model/student/exceptions/DuplicateExamsException.java new file mode 100644 index 00000000000..d17c364720a --- /dev/null +++ b/src/main/java/seedu/address/model/student/exceptions/DuplicateExamsException.java @@ -0,0 +1,11 @@ +package seedu.address.model.student.exceptions; + +/** + * Signals that the operation will result in duplicate Exams (Exams are considered duplicates if they have the same + * description, start time, end time, weightage, status, and grade). + */ +public class DuplicateExamsException extends RuntimeException { + public DuplicateExamsException() { + super("The exam already exists"); + } +} diff --git a/src/main/java/seedu/address/model/student/exceptions/DuplicateLessonException.java b/src/main/java/seedu/address/model/student/exceptions/DuplicateLessonException.java new file mode 100644 index 00000000000..3683aad5e29 --- /dev/null +++ b/src/main/java/seedu/address/model/student/exceptions/DuplicateLessonException.java @@ -0,0 +1,11 @@ +package seedu.address.model.student.exceptions; + +/** + * Signals that the operation will result in duplicate Lessons (Lesson are considered duplicates if they have the same + * title, startTime, and endTime). + */ +public class DuplicateLessonException extends RuntimeException { + public DuplicateLessonException() { + super("Operation would result in duplicate lessons"); + } +} diff --git a/src/main/java/seedu/address/model/student/exceptions/DuplicateStudentException.java b/src/main/java/seedu/address/model/student/exceptions/DuplicateStudentException.java new file mode 100644 index 00000000000..67755d556f1 --- /dev/null +++ b/src/main/java/seedu/address/model/student/exceptions/DuplicateStudentException.java @@ -0,0 +1,11 @@ +package seedu.address.model.student.exceptions; + +/** + * Signals that the operation will result in duplicate Students (Students are considered + * duplicates if they have the same identity). + */ +public class DuplicateStudentException extends RuntimeException { + public DuplicateStudentException() { + super("Operation would result in duplicate students"); + } +} diff --git a/src/main/java/seedu/address/model/student/exceptions/EntryNotFoundException.java b/src/main/java/seedu/address/model/student/exceptions/EntryNotFoundException.java new file mode 100644 index 00000000000..3270745c7e6 --- /dev/null +++ b/src/main/java/seedu/address/model/student/exceptions/EntryNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.student.exceptions; + +/** + * Signals that the operation is unable to find the specified entry. + */ +public class EntryNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/student/exceptions/LessonNotFoundException.java b/src/main/java/seedu/address/model/student/exceptions/LessonNotFoundException.java new file mode 100644 index 00000000000..c99bc2605a0 --- /dev/null +++ b/src/main/java/seedu/address/model/student/exceptions/LessonNotFoundException.java @@ -0,0 +1,10 @@ +package seedu.address.model.student.exceptions; + +/** + * Signals that the operation will result in lessons with conflicting timeslots + */ +public class LessonNotFoundException extends RuntimeException { + public LessonNotFoundException() { + super("Sorry, the lesson does not exist"); + } +} diff --git a/src/main/java/seedu/address/model/student/exceptions/StudentNotFoundException.java b/src/main/java/seedu/address/model/student/exceptions/StudentNotFoundException.java new file mode 100644 index 00000000000..2b41e9e0296 --- /dev/null +++ b/src/main/java/seedu/address/model/student/exceptions/StudentNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.student.exceptions; + +/** + * Signals that the operation is unable to find the specified student. + */ +public class StudentNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java index b0ea7e7dad7..3550d00e8ac 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -10,7 +10,10 @@ public class Tag { public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric"; - public static final String VALIDATION_REGEX = "\\p{Alnum}+"; + public static final String MESSAGE_TAG_LENGTH = "Tag length should be below 30 characters"; + public static final String VALIDATION_REGEX = "[\\p{Alnum} ]+"; + + public static final int MAX_TAG_LENGTH = 30; public final String tagName; @@ -22,6 +25,7 @@ public class Tag { public Tag(String tagName) { requireNonNull(tagName); checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS); + checkArgument(isValidTagLength(tagName), MESSAGE_TAG_LENGTH); this.tagName = tagName; } @@ -32,6 +36,17 @@ public static boolean isValidTagName(String test) { return test.matches(VALIDATION_REGEX); } + /** + * Returns true if a given string is a valid tag length. + */ + public static boolean isValidTagLength(String test) { + // tag length must be below 30 characters + if (test.length() > MAX_TAG_LENGTH) { + return false; + } + return true; + } + @Override public boolean equals(Object other) { return other == this // short circuit if same object diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..bfbdd1ab955 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -6,44 +6,45 @@ import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; +import seedu.address.model.student.Address; +import seedu.address.model.student.Email; +import seedu.address.model.student.Name; +import seedu.address.model.student.Phone; +import seedu.address.model.student.Student; import seedu.address.model.tag.Tag; /** * Contains utility methods for populating {@code AddressBook} with sample data. */ public class SampleDataUtil { - public static Person[] getSamplePersons() { - return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), + public static Student[] getSamplePersons() { + return new Student[] { + new Student(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), + getTagSet("primary6")), + new Student(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), + getTagSet("NUS", "year1")), + new Student(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), + getTagSet("ACJC", "J1")), + new Student(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), + getTagSet("sec4") + ), + new Student(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), + getTagSet("sec3")), + new Student(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) + getTagSet("NUS", "year3")) }; } public static ReadOnlyAddressBook getSampleAddressBook() { AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); + for (Student samplePerson : getSamplePersons()) { + sampleAb.addStudent(samplePerson); } return sampleAb; } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedExam.java b/src/main/java/seedu/address/storage/JsonAdaptedExam.java new file mode 100644 index 00000000000..a59f8d8d6fd --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedExam.java @@ -0,0 +1,53 @@ +package seedu.address.storage; +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.model.student.Exam; +import seedu.address.model.student.Grade; + +/** + * Jackson-friendly version of {@link Exam}. + */ +public class JsonAdaptedExam { + private final String description; + private final LocalDateTime startTime; + private final LocalDateTime endTime; + private final Double weightage; + private Grade grade; + + /** + * Constructs a {@code Exam} with the given exam details. + */ + @JsonCreator + public JsonAdaptedExam(@JsonProperty("description") String description, + @JsonProperty("startTime") LocalDateTime startTime, + @JsonProperty("endTime") LocalDateTime endTime, + @JsonProperty("weightage") Double weightage, + @JsonProperty("grade") Grade grade) { + this.description = description; + this.startTime = startTime; + this.endTime = endTime; + this.weightage = weightage; + this.grade = grade; + } + /** + * Constructs a {@code Exam} with the given exam object. + */ + public JsonAdaptedExam(Exam source) { + description = source.getDescription(); + startTime = source.getStartTime(); + endTime = source.getEndTime(); + weightage = source.getWeightage(); + grade = source.getGrade(); + } + + /** + * Converts this Jackson-friendly adapted homework object into the model's {@code exam} object. + */ + public Exam toModelType() { + Exam exam = new Exam(description, startTime, endTime, weightage, grade); + return exam; + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedHomework.java b/src/main/java/seedu/address/storage/JsonAdaptedHomework.java new file mode 100644 index 00000000000..0e2aa79fb9f --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedHomework.java @@ -0,0 +1,81 @@ +package seedu.address.storage; + +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.model.student.Homework; +import seedu.address.model.student.Homework.Status; + +/** + * Jackson-friendly version of {@link Homework}. + */ +public class JsonAdaptedHomework { + private final String description; + private final LocalDateTime deadline; + private final Status status; + + /** + * Constructs a {@code JsonAdaptedHomework} with the given homework details. + */ + @JsonCreator + public JsonAdaptedHomework(@JsonProperty("description") String description, + @JsonProperty("deadline") LocalDateTime deadline, + @JsonProperty("status") Status status) { + this.description = description; + this.deadline = deadline; + this.status = status; + } + + /** + * Converts a given {@code Homework} into this class for Jackson use. + */ + public JsonAdaptedHomework(Homework source) { + description = source.getDescription(); + deadline = source.getDeadline(); + status = source.getStatus(); + } + + @JsonProperty("description") + public String getDescription() { + return description; + } + + @JsonProperty("deadline") + public LocalDateTime getDeadline() { + return deadline; + } + + @JsonProperty("status") + public Status getStatus() { + return status; + } + + /** + * Converts this Jackson-friendly adapted homework object into the model's {@code Homework} object. + */ + public Homework toModelType() throws IllegalArgumentException { + Homework homework = new Homework(description, deadline); + if (status == Status.COMPLETED) { + homework.markAsDone(); + } else { + homework.markAsUndone(); + } + return homework; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof JsonAdaptedHomework // instanceof handles nulls + && description.equals(((JsonAdaptedHomework) other).description) + && deadline.equals(((JsonAdaptedHomework) other).deadline) + && status.equals(((JsonAdaptedHomework) other).status)); // state check + } + + @Override + public int hashCode() { + return description.hashCode() + deadline.hashCode() + status.hashCode(); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedLesson.java b/src/main/java/seedu/address/storage/JsonAdaptedLesson.java new file mode 100644 index 00000000000..e218813a472 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedLesson.java @@ -0,0 +1,63 @@ +package seedu.address.storage; + +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.model.student.Homework; +import seedu.address.model.student.Lesson; + +/** + * Jackson-friendly version of {@link Homework}. + */ +public class JsonAdaptedLesson { + + private final String title; + private final LocalDateTime startTime; + private final LocalDateTime endTime; + + /** + * Constructs a {@code JsonAdaptedHomework} with the given homework details. + */ + @JsonCreator + public JsonAdaptedLesson(@JsonProperty("title") String title, + @JsonProperty("startTime") LocalDateTime startTime, + @JsonProperty("endTime") LocalDateTime endTime) { + this.title = title; + this.startTime = startTime; + this.endTime = endTime; + } + + /** + * Converts a given {@code Lesson} into this class for Jackson use. + */ + public JsonAdaptedLesson(Lesson source) { + title = source.getTitle(); + startTime = source.getStartTime(); + endTime = source.getEndTime(); + } + + @JsonProperty("title") + public String getTitle() { + return title; + } + + @JsonProperty("startTime") + public LocalDateTime getStartTime() { + return startTime; + } + + @JsonProperty("endTime") + public LocalDateTime getEndTime() { + return endTime; + } + + /** + * Converts this Jackson-friendly adapted lesson object into the model's {@code Lesson} object. + */ + public Lesson toModelType() { + Lesson lesson = new Lesson(title, startTime, endTime); + return lesson; + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java deleted file mode 100644 index a6321cec2ea..00000000000 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ /dev/null @@ -1,109 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Jackson-friendly version of {@link Person}. - */ -class JsonAdaptedPerson { - - public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; - - private final String name; - private final String phone; - private final String email; - private final String address; - private final List tagged = new ArrayList<>(); - - /** - * Constructs a {@code JsonAdaptedPerson} with the given person details. - */ - @JsonCreator - public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, - @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tagged") List tagged) { - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - if (tagged != null) { - this.tagged.addAll(tagged); - } - } - - /** - * Converts a given {@code Person} into this class for Jackson use. - */ - public JsonAdaptedPerson(Person source) { - name = source.getName().fullName; - phone = source.getPhone().value; - email = source.getEmail().value; - address = source.getAddress().value; - tagged.addAll(source.getTags().stream() - .map(JsonAdaptedTag::new) - .collect(Collectors.toList())); - } - - /** - * Converts this Jackson-friendly adapted person object into the model's {@code Person} object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted person. - */ - public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); - for (JsonAdaptedTag tag : tagged) { - personTags.add(tag.toModelType()); - } - - if (name == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); - } - if (!Name.isValidName(name)) { - throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); - } - final Name modelName = new Name(name); - - if (phone == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); - } - if (!Phone.isValidPhone(phone)) { - throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS); - } - final Phone modelPhone = new Phone(phone); - - if (email == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); - } - if (!Email.isValidEmail(email)) { - throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS); - } - final Email modelEmail = new Email(email); - - if (address == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); - } - if (!Address.isValidAddress(address)) { - throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); - } - final Address modelAddress = new Address(address); - - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedStudent.java b/src/main/java/seedu/address/storage/JsonAdaptedStudent.java new file mode 100644 index 00000000000..57220ecfc9c --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedStudent.java @@ -0,0 +1,154 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.student.Address; +import seedu.address.model.student.Email; +import seedu.address.model.student.Exam; +import seedu.address.model.student.Homework; +import seedu.address.model.student.Lesson; +import seedu.address.model.student.Name; +import seedu.address.model.student.Phone; +import seedu.address.model.student.Student; +import seedu.address.model.tag.Tag; + +/** + * Jackson-friendly version of {@link Student}. + */ +class JsonAdaptedStudent { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Student's %s field is missing!"; + + private final String name; + private final String phone; + private final String email; + private final String address; + private final List tagged = new ArrayList<>(); + private final List homeworkList = new ArrayList<>(); + private final List lessonList = new ArrayList<>(); + private final List examList = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedStudent} with the given student details. + */ + @JsonCreator + public JsonAdaptedStudent(@JsonProperty("name") String name, @JsonProperty("phone") String phone, + @JsonProperty("email") String email, @JsonProperty("address") String address, + @JsonProperty("tagged") List tagged, + @JsonProperty("homeworks") List homeworkList, + @JsonProperty("lessons") List lessonList, + @JsonProperty("exams") List examList) { + this.name = name; + this.phone = phone; + this.email = email; + this.address = address; + if (tagged != null) { + this.tagged.addAll(tagged); + } + if (homeworkList != null) { + this.homeworkList.addAll(homeworkList); + } + if (lessonList != null) { + this.lessonList.addAll(lessonList); + } + if (examList != null) { + this.examList.addAll(examList); + } + } + + /** + * Converts a given {@code Student} into this class for Jackson use. + */ + public JsonAdaptedStudent(Student source) { + name = source.getName().fullName; + phone = source.getPhone().value; + email = source.getEmail().value; + address = source.getAddress().value; + tagged.addAll(source.getTags().stream() + .map(JsonAdaptedTag::new) + .collect(Collectors.toList())); + homeworkList.addAll(source.getHomeworkList().stream() + .map(JsonAdaptedHomework::new) + .collect(Collectors.toList())); + lessonList.addAll(source.getLessonsList().stream() + .map(JsonAdaptedLesson::new) + .collect(Collectors.toList())); + examList.addAll(source.getExamsList().stream() + .map(JsonAdaptedExam::new) + .collect(Collectors.toList())); + } + + /** + * Converts this Jackson-friendly adapted student object into the model's {@code Student} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted student. + */ + public Student toModelType() throws IllegalValueException { + final List studentTags = new ArrayList<>(); + for (JsonAdaptedTag tag : tagged) { + studentTags.add(tag.toModelType()); + } + + // Convert homeworkList to modelHomeworkList + final List modelHomeworkList = new ArrayList<>(); + for (JsonAdaptedHomework homework : homeworkList) { + modelHomeworkList.add(homework.toModelType()); + } + + // Convert lessonList to modelLessonList + final List modelLessonList = new ArrayList<>(); + for (JsonAdaptedLesson lesson : lessonList) { + modelLessonList.add(lesson.toModelType()); + } + + // Convert examList to modelExamList + final List modelExamList = new ArrayList<>(); + for (JsonAdaptedExam exam : examList) { + modelExamList.add(exam.toModelType()); + } + + if (name == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + if (!Name.isValidName(name)) { + throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + } + final Name modelName = new Name(name); + + if (phone == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); + } + if (!Phone.isValidPhone(phone)) { + throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS); + } + final Phone modelPhone = new Phone(phone); + + if (email == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); + } + if (!Email.isValidEmail(email)) { + throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS); + } + final Email modelEmail = new Email(email); + + if (address == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); + } + if (!Address.isValidAddress(address)) { + throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); + } + final Address modelAddress = new Address(address); + + final Set modelTags = new HashSet<>(studentTags); + return new Student(modelName, modelPhone, modelEmail, modelAddress, modelTags, modelHomeworkList, + modelLessonList, modelExamList); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java index 0df22bdb754..82f9f440f8c 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedTag.java @@ -39,7 +39,7 @@ public String getTagName() { * @throws IllegalValueException if there were any data constraints violated in the adapted tag. */ public Tag toModelType() throws IllegalValueException { - if (!Tag.isValidTagName(tagName)) { + if (!Tag.isValidTagName(tagName) || !Tag.isValidTagLength(tagName)) { throw new IllegalValueException(Tag.MESSAGE_CONSTRAINTS); } return new Tag(tagName); diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java index 5efd834091d..d8d2d8be0af 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java @@ -11,7 +11,7 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.address.model.student.Student; /** * An Immutable AddressBook that is serializable to JSON format. @@ -21,13 +21,13 @@ class JsonSerializableAddressBook { public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; - private final List persons = new ArrayList<>(); + private final List persons = new ArrayList<>(); /** * Constructs a {@code JsonSerializableAddressBook} with the given persons. */ @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { + public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { this.persons.addAll(persons); } @@ -37,7 +37,7 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; - public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; + public static final String USERGUIDE_URL = "https://ay2223s2-cs2103t-w13-4.github.io/tp/"; + public static final String HELP_MESSAGE = "Refer to the user guide of TutorPro:\n" + USERGUIDE_URL; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); private static final String FXML = "HelpWindow.fxml"; @@ -64,6 +64,7 @@ public HelpWindow() { */ public void show() { logger.fine("Showing help page about the application."); + getRoot().setResizable(false); getRoot().show(); getRoot().centerOnScreen(); } @@ -99,4 +100,18 @@ private void copyUrl() { url.putString(USERGUIDE_URL); clipboard.setContent(url); } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof HelpWindow)) { + return false; + } + + HelpWindow other = (HelpWindow) obj; + return helpMessage.getText().equals(other.helpMessage.getText()); + } } diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 9106c3aa6e5..2c48e6095b2 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -8,6 +8,7 @@ import javafx.scene.control.TextInputControl; import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; +import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import javafx.stage.Stage; import seedu.address.commons.core.GuiSettings; @@ -16,6 +17,9 @@ import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.ui.command.CommandBox; +import seedu.address.ui.command.ResultDisplay; +import seedu.address.ui.detail.DetailedInfoRegion; /** * The Main Window. Provides the basic application layout containing @@ -28,12 +32,13 @@ public class MainWindow extends UiPart { private final Logger logger = LogsCenter.getLogger(getClass()); private Stage primaryStage; - private Logic logic; + private final Logic logic; // Independent Ui parts residing in this Ui container - private PersonListPanel personListPanel; + private StudentListPanel studentListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; + private DetailedInfoRegion detailedInfoRegion; @FXML private StackPane commandBoxPlaceholder; @@ -50,6 +55,9 @@ public class MainWindow extends UiPart { @FXML private StackPane statusbarPlaceholder; + @FXML + private StackPane detailedInfoRegionPlaceholder; + /** * Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}. */ @@ -63,6 +71,9 @@ public MainWindow(Stage primaryStage, Logic logic) { // Configure the UI setWindowDefaultSize(logic.getGuiSettings()); + // set the resiza + primaryStage.setResizable(false); + setAccelerators(); helpWindow = new HelpWindow(); @@ -110,12 +121,15 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { * Fills up all the placeholders of this window. */ void fillInnerParts() { - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + studentListPanel = new StudentListPanel(logic.getFilteredPersonList(), this); + personListPanelPlaceholder.getChildren().add(studentListPanel.getRoot()); resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); + detailedInfoRegion = new DetailedInfoRegion("Welcome to the TutorPro!"); + detailedInfoRegionPlaceholder.getChildren().add(detailedInfoRegion.getRoot()); + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); @@ -163,8 +177,8 @@ private void handleExit() { primaryStage.hide(); } - public PersonListPanel getPersonListPanel() { - return personListPanel; + public StudentListPanel getPersonListPanel() { + return studentListPanel; } /** @@ -178,6 +192,9 @@ private CommandResult executeCommand(String commandText) throws CommandException logger.info("Result: " + commandResult.getFeedbackToUser()); resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + detailedInfoRegion = new DetailedInfoRegion("Welcome to the TutorPro!"); + detailedInfoRegionPlaceholder.getChildren().add(detailedInfoRegion.getRoot()); + if (commandResult.isShowHelp()) { handleHelp(); } @@ -187,10 +204,23 @@ private CommandResult executeCommand(String commandText) throws CommandException } return commandResult; - } catch (CommandException | ParseException e) { + } catch (CommandException | ParseException | IllegalArgumentException e) { logger.info("Invalid command: " + commandText); resultDisplay.setFeedbackToUser(e.getMessage()); throw e; } } + + public void setDetailedContent(UiPart detailedContent) { + detailedInfoRegion.setDetailedContent(detailedContent); + } + + /** + * Sets the header bar of the detailed info section to display the given text. + * + * @param textToDisplay the text to display + */ + public void setDetailedHeaderBar(String textToDisplay, String iconPath) { + detailedInfoRegion.setHeaderBar(textToDisplay, iconPath); + } } diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java deleted file mode 100644 index 7fc927bc5d9..00000000000 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ /dev/null @@ -1,77 +0,0 @@ -package seedu.address.ui; - -import java.util.Comparator; - -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.layout.FlowPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Region; -import seedu.address.model.person.Person; - -/** - * An UI component that displays information of a {@code Person}. - */ -public class PersonCard extends UiPart { - - private static final String FXML = "PersonListCard.fxml"; - - /** - * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. - * As a consequence, UI elements' variable names cannot be set to such keywords - * or an exception will be thrown by JavaFX during runtime. - * - * @see The issue on AddressBook level 4 - */ - - public final Person person; - - @FXML - private HBox cardPane; - @FXML - private Label name; - @FXML - private Label id; - @FXML - private Label phone; - @FXML - private Label address; - @FXML - private Label email; - @FXML - private FlowPane tags; - - /** - * Creates a {@code PersonCode} with the given {@code Person} and index to display. - */ - public PersonCard(Person person, int displayedIndex) { - super(FXML); - this.person = person; - id.setText(displayedIndex + ". "); - name.setText(person.getName().fullName); - phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); - person.getTags().stream() - .sorted(Comparator.comparing(tag -> tag.tagName)) - .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof PersonCard)) { - return false; - } - - // state check - PersonCard card = (PersonCard) other; - return id.getText().equals(card.id.getText()) - && person.equals(card.person); - } -} diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java deleted file mode 100644 index f4c501a897b..00000000000 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ /dev/null @@ -1,49 +0,0 @@ -package seedu.address.ui; - -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.control.ListCell; -import javafx.scene.control.ListView; -import javafx.scene.layout.Region; -import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; - -/** - * Panel containing the list of persons. - */ -public class PersonListPanel extends UiPart { - private static final String FXML = "PersonListPanel.fxml"; - private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); - - @FXML - private ListView personListView; - - /** - * Creates a {@code PersonListPanel} with the given {@code ObservableList}. - */ - public PersonListPanel(ObservableList personList) { - super(FXML); - personListView.setItems(personList); - personListView.setCellFactory(listView -> new PersonListViewCell()); - } - - /** - * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. - */ - class PersonListViewCell extends ListCell { - @Override - protected void updateItem(Person person, boolean empty) { - super.updateItem(person, empty); - - if (empty || person == null) { - setGraphic(null); - setText(null); - } else { - setGraphic(new PersonCard(person, getIndex() + 1).getRoot()); - } - } - } - -} diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/seedu/address/ui/StatusBarFooter.java index b577f829423..063e253c928 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/seedu/address/ui/StatusBarFooter.java @@ -25,4 +25,17 @@ public StatusBarFooter(Path saveLocation) { saveLocationStatus.setText(Paths.get(".").resolve(saveLocation).toString()); } + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof StatusBarFooter)) { + return false; + } + + StatusBarFooter other = (StatusBarFooter) obj; + return saveLocationStatus.getText().equals(other.saveLocationStatus.getText()); + } } diff --git a/src/main/java/seedu/address/ui/StudentCard.java b/src/main/java/seedu/address/ui/StudentCard.java new file mode 100644 index 00000000000..fa25c5118d5 --- /dev/null +++ b/src/main/java/seedu/address/ui/StudentCard.java @@ -0,0 +1,148 @@ +package seedu.address.ui; + +import java.util.Comparator; + +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.student.Student; +import seedu.address.ui.detail.ProfileContent; +import seedu.address.ui.exam.EmptyExamsContent; +import seedu.address.ui.exam.FilledExamsContent; +import seedu.address.ui.homework.EmptyHomeworkContent; +import seedu.address.ui.homework.FilledHomeworkContent; +import seedu.address.ui.lesson.EmptyLessonsContent; +import seedu.address.ui.lesson.FilledLessonsContent; + +/** + * A UI component that displays information of a {@code Person}. + */ +public class StudentCard extends UiPart { + private static final String FXML = "StudentListCard.fxml"; + private static final String PROFILE_ICON = "images/profile_icon.png"; + private static final String HOMEWORK_ICON = "images/homework_icon.png"; + private static final String LESSONS_ICON = "images/lessons_icon.png"; + private static final String EXAMS_ICON = "images/exams_icon.png"; + + private final MainWindow mainWindow; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + @FXML + private HBox cardPane; + @FXML + private Label name; + @FXML + private Label id; + @FXML + private FlowPane tags; + @FXML + private Button viewProfileButton; + @FXML + private Button viewSchoolTasksButton; + @FXML + private Button viewLessonsButton; + @FXML + private Button viewExamsButton; + + private final Student student; + + /** + * Creates a {@code PersonCode} with the given {@code Person} and index to display. + */ + public StudentCard(Student student, int displayedIndex, MainWindow mainWindow) { + super(FXML); + this.student = student; + this.mainWindow = mainWindow; + + id.setText(displayedIndex + ". "); + name.setText(student.getName().fullName); + student.getTags().stream() + .sorted(Comparator.comparing(tag -> tag.tagName)) + .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + + viewProfileButton.setOnAction(event -> handleViewProfileClick()); + viewSchoolTasksButton.setOnAction(event -> handleViewHomeworkClick()); + viewLessonsButton.setOnAction(event -> handleViewLessonsClick()); + viewExamsButton.setOnAction(event -> handleViewExamsClick()); + } + + /** + * Handles the view profile button click event. + * Displays the student profile. + */ + @FXML + private void handleViewProfileClick() { + mainWindow.setDetailedHeaderBar(String.format("Student Profile: %s", + student.getName().getFirstName()), PROFILE_ICON); + mainWindow.setDetailedContent(new ProfileContent(student)); + } + + /** + * Handles the view school tasks button click event. + * Displays the student homework list with a pie chart of the homework completion status. + */ + private void handleViewHomeworkClick() { + mainWindow.setDetailedHeaderBar(String.format("Homework List: %s", + student.getName().getFirstName()), HOMEWORK_ICON); + if (student.getHomeworkList().isEmpty()) { + mainWindow.setDetailedContent(new EmptyHomeworkContent(student)); + } else { + mainWindow.setDetailedContent(new FilledHomeworkContent(student)); + } + } + + /** + * Handles the view student lessons button click event + * Displays the student past lessons history and upcoming lessons. + */ + private void handleViewLessonsClick() { + mainWindow.setDetailedHeaderBar(String.format("Lessons List: %s", + student.getName().getFirstName()), LESSONS_ICON); + if (student.getLessonsList().isEmpty()) { + mainWindow.setDetailedContent(new EmptyLessonsContent(student)); + } else { + mainWindow.setDetailedContent(new FilledLessonsContent(student)); + } + } + + /** + * Handles the view student exams button click event + * Displays all the student exams and upcoming exams in a quick glance. + */ + private void handleViewExamsClick() { + mainWindow.setDetailedHeaderBar(String.format("Exams List: %s", + student.getName().getFirstName()), EXAMS_ICON); + if (student.getExamsList().isEmpty()) { + mainWindow.setDetailedContent(new EmptyExamsContent(student)); + } else { + mainWindow.setDetailedContent(new FilledExamsContent(student)); + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof StudentCard)) { + return false; + } + + // state check + StudentCard card = (StudentCard) other; + return id.getText().equals(card.id.getText()) + && student.equals(card.student); + } +} diff --git a/src/main/java/seedu/address/ui/StudentListPanel.java b/src/main/java/seedu/address/ui/StudentListPanel.java new file mode 100644 index 00000000000..7fb1c7972f0 --- /dev/null +++ b/src/main/java/seedu/address/ui/StudentListPanel.java @@ -0,0 +1,67 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.student.Student; + +/** + * Panel containing the list of persons. + */ +public class StudentListPanel extends UiPart { + private static final String FXML = "StudentListPanel.fxml"; + private final MainWindow mainWindow; + private final Logger logger = LogsCenter.getLogger(StudentListPanel.class); + + @FXML + private ListView personListView; + + /** + * Creates a {@code StudentListPanel} with the given {@code ObservableList}. + */ + public StudentListPanel(ObservableList personList, MainWindow mainWindow) { + super(FXML); + this.mainWindow = mainWindow; + personListView.setItems(personList); + personListView.setCellFactory(listView -> new PersonListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code StudentCard}. + */ + class PersonListViewCell extends ListCell { + @Override + protected void updateItem(Student person, boolean empty) { + super.updateItem(person, empty); + + if (empty || person == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new StudentCard(person, getIndex() + 1, mainWindow).getRoot()); + } + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof StudentListPanel)) { + return false; + } + + // state check + StudentListPanel panel = (StudentListPanel) other; + return personListView.equals(panel.personListView); + } +} diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java index fdf024138bc..82e24846899 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/UiManager.java @@ -22,7 +22,7 @@ public class UiManager implements Ui { private static final Logger logger = LogsCenter.getLogger(UiManager.class); private static final String ICON_APPLICATION = "/images/address_book_32.png"; - private Logic logic; + private final Logic logic; private MainWindow mainWindow; /** @@ -65,7 +65,7 @@ void showAlertDialogAndWait(Alert.AlertType type, String title, String headerTex private static void showAlertDialogAndWait(Stage owner, AlertType type, String title, String headerText, String contentText) { final Alert alert = new Alert(type); - alert.getDialogPane().getStylesheets().add("view/DarkTheme.css"); + alert.getDialogPane().getStylesheets().add("view/stylesheet/LightTheme.css"); alert.initOwner(owner); alert.setTitle(title); alert.setHeaderText(headerText); @@ -84,5 +84,4 @@ private void showFatalErrorDialogAndShutdown(String title, Throwable e) { Platform.exit(); System.exit(1); } - } diff --git a/src/main/java/seedu/address/ui/UiUtil.java b/src/main/java/seedu/address/ui/UiUtil.java new file mode 100644 index 00000000000..8c90e7bd766 --- /dev/null +++ b/src/main/java/seedu/address/ui/UiUtil.java @@ -0,0 +1,63 @@ +package seedu.address.ui; + +/** + * A class that contains the UI-related constants and utility methods. + */ +public class UiUtil { + private static final String LINE_BREAK = System.lineSeparator(); + private static final String INDENT = " "; + private static final String SPACE = " "; + + /** + * Adds line breaks to a string so that each line is at most n characters long. + * + * @param text The string to be formatted. + * @param n The maximum number of characters per line. + * @return The formatted string. + */ + public static String addLineBreaksWithIndent(String text, int n) { + String[] words = text.split(" "); + StringBuilder sb = new StringBuilder(); + + int lineLength = 0; + for (String word : words) { + if (lineLength + word.length() > n) { + sb.append(LINE_BREAK); + sb.append(INDENT); + sb.append(word); + lineLength = 0; + } else { + sb.append(SPACE); + sb.append(word); + lineLength += word.length() + 1; + } + } + return sb.toString(); + } + + /** + * Adds line breaks to a string so that each line is at most n characters long. + * + * @param text The string to be formatted. + * @param n The maximum number of characters per line. + * @return The formatted string. + */ + public static String addLineBreaksWithoutIndent(String text, int n) { + String[] words = text.split(" "); + StringBuilder sb = new StringBuilder(); + + int lineLength = 0; + for (String word : words) { + if (lineLength + word.length() > n) { + sb.append(LINE_BREAK); + sb.append(word); + lineLength = 0; + } else { + sb.append(SPACE); + sb.append(word); + lineLength += word.length() + 1; + } + } + return sb.toString(); + } +} diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/command/CommandBox.java similarity index 90% rename from src/main/java/seedu/address/ui/CommandBox.java rename to src/main/java/seedu/address/ui/command/CommandBox.java index 9e75478664b..c75ba6ac1bf 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/address/ui/command/CommandBox.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.address.ui.command; import javafx.collections.ObservableList; import javafx.fxml.FXML; @@ -7,14 +7,15 @@ import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.ui.UiPart; /** * The UI component that is responsible for receiving user command inputs. */ public class CommandBox extends UiPart { - public static final String ERROR_STYLE_CLASS = "error"; private static final String FXML = "CommandBox.fxml"; + private static final String PROMPT_TEXT = "Enter command here"; private final CommandExecutor commandExecutor; @@ -23,11 +24,15 @@ public class CommandBox extends UiPart { /** * Creates a {@code CommandBox} with the given {@code CommandExecutor}. + * + * @param commandExecutor The command executor to be used. */ public CommandBox(CommandExecutor commandExecutor) { super(FXML); this.commandExecutor = commandExecutor; + // calls #setStyleToDefault() whenever there is a change to the text of the command box. + commandTextField.setPromptText(PROMPT_TEXT); commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault()); } diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/seedu/address/ui/command/ResultDisplay.java similarity index 61% rename from src/main/java/seedu/address/ui/ResultDisplay.java rename to src/main/java/seedu/address/ui/command/ResultDisplay.java index 7d98e84eedf..8d3074cfa51 100644 --- a/src/main/java/seedu/address/ui/ResultDisplay.java +++ b/src/main/java/seedu/address/ui/command/ResultDisplay.java @@ -1,28 +1,35 @@ -package seedu.address.ui; +package seedu.address.ui.command; import static java.util.Objects.requireNonNull; import javafx.fxml.FXML; import javafx.scene.control.TextArea; import javafx.scene.layout.Region; +import seedu.address.ui.UiPart; /** - * A ui for the status bar that is displayed at the header of the application. + * A UI for the status bar that is displayed at the header of the application. */ public class ResultDisplay extends UiPart { - private static final String FXML = "ResultDisplay.fxml"; @FXML private TextArea resultDisplay; + /** + * Creates a {@code ResultDisplay} with the given {@code Stage}. + */ public ResultDisplay() { super(FXML); } + /** + * Sets the feedback to be displayed. + * + * @param feedbackToUser The feedback to be displayed. + */ public void setFeedbackToUser(String feedbackToUser) { requireNonNull(feedbackToUser); resultDisplay.setText(feedbackToUser); } - } diff --git a/src/main/java/seedu/address/ui/detail/DetailedContent.java b/src/main/java/seedu/address/ui/detail/DetailedContent.java new file mode 100644 index 00000000000..92171363b17 --- /dev/null +++ b/src/main/java/seedu/address/ui/detail/DetailedContent.java @@ -0,0 +1,15 @@ +package seedu.address.ui.detail; + +import javafx.scene.layout.Region; +import seedu.address.ui.UiPart; + +/** + * An abstract class that represents the detailed content of a student. + */ +public abstract class DetailedContent extends UiPart { + protected static final int MAX_LINE_LENGTH = 35; + + public DetailedContent(String fxml) { + super(fxml); + } +} diff --git a/src/main/java/seedu/address/ui/detail/DetailedInfoRegion.java b/src/main/java/seedu/address/ui/detail/DetailedInfoRegion.java new file mode 100644 index 00000000000..55e60b2c48a --- /dev/null +++ b/src/main/java/seedu/address/ui/detail/DetailedInfoRegion.java @@ -0,0 +1,68 @@ +package seedu.address.ui.detail; + +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import seedu.address.commons.core.LogsCenter; +import seedu.address.ui.UiPart; + +/** + * An ui for the status bar that is displayed at the header of the application. + */ +public class DetailedInfoRegion extends UiPart { + private static final Logger logger = LogsCenter.getLogger(DetailedInfoRegion.class); + private static final String FXML = "DetailedInfoRegion.fxml"; + private static final String DEFAULT_ICON_PATH = "/images/info_icon.png"; + private static final String DEFAULT_TITLE_TEXT = "Detailed Information"; + + @FXML + private StackPane headerBarPlaceholder; + @FXML + private StackPane detailedContentPlaceholder; + + /** + * Creates a {@code DetailedInfoRegion} with the given {@code Stage} and {@code Student}. + * + * @param textToDisplay The text to display in the header bar. + */ + public DetailedInfoRegion(String textToDisplay) { + super(FXML); + + HeaderBar headerBar = new HeaderBar(DEFAULT_TITLE_TEXT, DEFAULT_ICON_PATH); + headerBarPlaceholder.getChildren().add(headerBar.getRoot()); + + UiPart detailedContent = new WelcomeContent(); + detailedContentPlaceholder.getChildren().add(detailedContent.getRoot()); + } + + /** + * Sets the header bar to display the given text. + * + * @param titleText The text to display in the header bar. + * @param iconPath The path to the icon to display in the header bar. + */ + public void setHeaderBar(String titleText, String iconPath) { + HeaderBar headerBar = new HeaderBar(titleText, iconPath); + headerBarPlaceholder.getChildren().add(headerBar.getRoot()); + } + + /** + * Sets the detailed content to display the given UiPart. + * + * @param detailedContent The UiPart to display. + */ + public void setDetailedContent(UiPart detailedContent) { + detailedContentPlaceholder.getChildren().clear(); + detailedContentPlaceholder.getChildren().add(detailedContent.getRoot()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DetailedInfoRegion // instanceof handles nulls + && headerBarPlaceholder.equals(((DetailedInfoRegion) other).headerBarPlaceholder) + && detailedContentPlaceholder.equals(((DetailedInfoRegion) other).detailedContentPlaceholder)); + } +} diff --git a/src/main/java/seedu/address/ui/detail/HeaderBar.java b/src/main/java/seedu/address/ui/detail/HeaderBar.java new file mode 100644 index 00000000000..1ee3bbd96c3 --- /dev/null +++ b/src/main/java/seedu/address/ui/detail/HeaderBar.java @@ -0,0 +1,43 @@ +package seedu.address.ui.detail; + +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.ui.UiPart; + +/** + * An ui for the status bar that is displayed at the header of the application. + */ +public class HeaderBar extends UiPart { + private static final Logger logger = LogsCenter.getLogger(HeaderBar.class); + private static final String FXML = "HeaderBar.fxml"; + + @FXML + private ImageView icon; + @FXML + private Label title; + + /** + * Creates a {@code HeaderBar} with the given {@code Stage} and {@code Student}. + * + * @param titleText The text to display in the header bar. + * @param iconPath The path to the icon to display in the header bar. + */ + public HeaderBar(String titleText, String iconPath) { + super(FXML); + title.setText(titleText); + icon.setImage(new Image(iconPath)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof HeaderBar // instanceof handles nulls + && title.getText().equals(((HeaderBar) other).title.getText())); // state check + } +} diff --git a/src/main/java/seedu/address/ui/detail/ProfileContent.java b/src/main/java/seedu/address/ui/detail/ProfileContent.java new file mode 100644 index 00000000000..2b947f1341f --- /dev/null +++ b/src/main/java/seedu/address/ui/detail/ProfileContent.java @@ -0,0 +1,55 @@ +package seedu.address.ui.detail; + +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.student.Student; +import seedu.address.ui.UiUtil; + +/** + * A UI component that displays detailed information of a {@code Student}. + */ +public class ProfileContent extends DetailedContent { + private static final Logger logger = LogsCenter.getLogger(ProfileContent.class); + private static final String FXML = "ProfileContent.fxml"; + private static final String NAME_LABEL = "Full Name: %s"; + private static final String PHONE_LABEL = "Phone Number: %s"; + private static final String ADDRESS_LABEL = "Address: %s"; + private static final String EMAIL_LABEL = "Email: %s"; + + @FXML + private Label name; + @FXML + private Label phone; + @FXML + private Label address; + @FXML + private Label email; + + /** + * Creates a {@code ProfileContent} with the given {@code Student}. + * + * @param student The student to display the profile content for. + */ + public ProfileContent(Student student) { + super(FXML); + + name.setText(UiUtil.addLineBreaksWithIndent( + String.format(NAME_LABEL, student.getName().fullName), MAX_LINE_LENGTH)); + phone.setText(UiUtil.addLineBreaksWithIndent( + String.format(PHONE_LABEL, student.getPhone().value), MAX_LINE_LENGTH)); + address.setText(UiUtil.addLineBreaksWithIndent( + String.format(ADDRESS_LABEL, student.getAddress().value), MAX_LINE_LENGTH)); + email.setText(UiUtil.addLineBreaksWithIndent( + String.format(EMAIL_LABEL, student.getEmail().value), MAX_LINE_LENGTH)); + } + + @Override + public boolean equals(Object obj) { + return obj == this // short circuit if same object + || (obj instanceof ProfileContent // instanceof handles nulls + && name.equals(((ProfileContent) obj).name)); // state check + } +} diff --git a/src/main/java/seedu/address/ui/detail/WelcomeContent.java b/src/main/java/seedu/address/ui/detail/WelcomeContent.java new file mode 100644 index 00000000000..ec09251c78b --- /dev/null +++ b/src/main/java/seedu/address/ui/detail/WelcomeContent.java @@ -0,0 +1,38 @@ +package seedu.address.ui.detail; + +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import seedu.address.commons.core.LogsCenter; +import seedu.address.ui.exam.EmptyExamsContent; + +/** + * An ui for the status bar that is displayed at the header of the application. + * This is the welcome screen that is displayed when the application is first opened. + */ +public class WelcomeContent extends DetailedContent { + private static final Logger logger = LogsCenter.getLogger(WelcomeContent.class); + private static final String FXML = "WelcomeContent.fxml"; + private static final String WELCOME_MESSAGE = "Welcome to TutorPro!\n\n" + + "Your one-stop solution for managing\n" + + "all your students and lessons."; + + @FXML + private Label welcomeMessage; + + /** + * Creates a {@code WelcomeContent} with the given {@code Stage} and {@code Student}. + */ + public WelcomeContent() { + super(FXML); + welcomeMessage.setText(WELCOME_MESSAGE); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EmptyExamsContent // instanceof handles nulls + && welcomeMessage.getText().equals(((WelcomeContent) other).welcomeMessage.getText())); // state check + } +} diff --git a/src/main/java/seedu/address/ui/exam/EmptyExamsContent.java b/src/main/java/seedu/address/ui/exam/EmptyExamsContent.java new file mode 100644 index 00000000000..7cb643b4f41 --- /dev/null +++ b/src/main/java/seedu/address/ui/exam/EmptyExamsContent.java @@ -0,0 +1,47 @@ +package seedu.address.ui.exam; + +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.student.Student; +import seedu.address.ui.UiUtil; +import seedu.address.ui.lesson.EmptyLessonsContent; + +/** + * An empty UI component that is displayed when there is no exams to be displayed. + */ +public class EmptyExamsContent extends GeneralExamsContent { + private static final Logger logger = LogsCenter.getLogger(EmptyLessonsContent.class); + private static final String FXML = "EmptyContent.fxml"; + private static final String MESSAGE = "No exams to display for %s!"; + + @FXML + private Label message; + + /** + * Creates an empty {@code FilledLessonsContent} with the given {@code Student}. + * + * @param student The student to display the empty exams content for. + */ + public EmptyExamsContent(Student student) { + super(FXML); + String textToDisplay = String.format(MESSAGE, student.getName().fullName); + message.setText(UiUtil.addLineBreaksWithoutIndent(textToDisplay, MAX_LINE_LENGTH)); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof EmptyExamsContent)) { + return false; + } + + EmptyExamsContent other = (EmptyExamsContent) obj; + return message.getText().equals(other.message.getText()); + } +} diff --git a/src/main/java/seedu/address/ui/exam/ExamCard.java b/src/main/java/seedu/address/ui/exam/ExamCard.java new file mode 100644 index 00000000000..01b07106d42 --- /dev/null +++ b/src/main/java/seedu/address/ui/exam/ExamCard.java @@ -0,0 +1,57 @@ +package seedu.address.ui.exam; + +import java.time.format.DateTimeFormatter; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.student.Exam; +import seedu.address.ui.UiPart; + +/** + * A UI component that displays information of a {@code Person}. + */ +public class ExamCard extends UiPart { + private static final DateTimeFormatter PRINT_FORMATTER = DateTimeFormatter.ofPattern("dd MMM yyyy HH:mm"); + private static final String FXML = "ExamListCard.fxml"; + + @FXML + private HBox cardPane; + @FXML + private Label title; + @FXML + private Label startTime; + @FXML + private Label endTime; + + /** + * Creates a {@code ExamCard} with the given {@code Exam} and index to display. + * + * @param exam the exam to be displayed + * @param id the index of the exam + */ + public ExamCard(Exam exam, int id) { + super(FXML); + title.setText(exam.getDescription()); + startTime.setText(exam.getStartTime().format(PRINT_FORMATTER)); + endTime.setText(exam.getEndTime().format(PRINT_FORMATTER)); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + // instanceof handles nulls + if (!(other instanceof ExamCard)) { + return false; + } + // state check + ExamCard card = (ExamCard) other; + return title.getText().equals(card.title.getText()) + && startTime.getText().equals(card.startTime.getText()) + && endTime.getText().equals(card.endTime.getText()); + } +} diff --git a/src/main/java/seedu/address/ui/exam/ExamsListPanel.java b/src/main/java/seedu/address/ui/exam/ExamsListPanel.java new file mode 100644 index 00000000000..91cf47bdd85 --- /dev/null +++ b/src/main/java/seedu/address/ui/exam/ExamsListPanel.java @@ -0,0 +1,68 @@ +package seedu.address.ui.exam; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.student.Exam; +import seedu.address.ui.UiPart; + +/** + * Panel containing the list of Homeworks. + */ +public class ExamsListPanel extends UiPart { + private static final Logger logger = LogsCenter.getLogger(ExamsListPanel.class); + private static final String FXML = "ExamsListPanel.fxml"; + + @FXML + private ListView examsListView; + + /** + * Creates a {@code HomeworkListPanel} with the given {@code ObservableList}. + * + * @param examsList The list of exams to display. + */ + public ExamsListPanel(ObservableList examsList) { + super(FXML); + examsListView.setItems(examsList); + examsListView.setCellFactory(listView -> new ExamListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Homework} using a {@code HomeworkCard}. + */ + static class ExamListViewCell extends ListCell { + @Override + protected void updateItem(Exam exam, boolean empty) { + super.updateItem(exam, empty); + + if (empty || exam == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new ExamCard(exam, getIndex() + 1).getRoot()); + } + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ExamsListPanel)) { + return false; + } + + // state check + ExamsListPanel otherExamsListPanel = (ExamsListPanel) other; + return examsListView.equals(otherExamsListPanel.examsListView); + } +} diff --git a/src/main/java/seedu/address/ui/exam/FilledExamsContent.java b/src/main/java/seedu/address/ui/exam/FilledExamsContent.java new file mode 100644 index 00000000000..837e3fa01f9 --- /dev/null +++ b/src/main/java/seedu/address/ui/exam/FilledExamsContent.java @@ -0,0 +1,76 @@ +package seedu.address.ui.exam; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.StackPane; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.student.Exam; +import seedu.address.model.student.Name; +import seedu.address.model.student.Student; +import seedu.address.ui.homework.FilledHomeworkContent; + +/** + * Panel containing the list of Exams. + */ +public class FilledExamsContent extends GeneralExamsContent { + private static final Logger logger = LogsCenter.getLogger(FilledHomeworkContent.class); + private static final String PAST_EXAMS_LIST_NAME = "Past Exams: "; + private static final String UPCOMING_EXAMS_LIST_NAME = "Upcoming Exams: "; + private static final String FXML = "ExamsContent.fxml"; + + private final Name studentName; + + @FXML + private Label name; + @FXML + private Label pastExamsListName; + @FXML + private StackPane pastExamsListPlaceholder; + @FXML + private Label upcomingExamsListName; + @FXML + private StackPane upcomingExamsListPlaceholder; + + /** + * Creates a {@code FilledHomeworkContent} with the given {@code Student}. + */ + public FilledExamsContent(Student student) { + super(FXML); + studentName = student.getName(); + + name.setText(String.format("First Name: %s", student.getName().getFirstName())); + pastExamsListName.setText(PAST_EXAMS_LIST_NAME); + + // Set the past exams list panel to display the past exams of the student + ObservableList pastExamsList = student.getPastExamsList(); + ExamsListPanel pastExamsListPanel = new ExamsListPanel(pastExamsList); + pastExamsListPlaceholder.getChildren().add(pastExamsListPanel.getRoot()); + + upcomingExamsListName.setText(UPCOMING_EXAMS_LIST_NAME); + + // Set the upcoming exams list panel to display the upcoming exams of the student + ObservableList upcomingExamsList = student.getUpcomingExamsList(); + ExamsListPanel upcomingExamsListPanel = new ExamsListPanel(upcomingExamsList); + upcomingExamsListPlaceholder.getChildren().add(upcomingExamsListPanel.getRoot()); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FilledExamsContent)) { + return false; + } + + // state check + FilledExamsContent card = (FilledExamsContent) other; + return studentName.equals(card.studentName); + } +} diff --git a/src/main/java/seedu/address/ui/exam/GeneralExamsContent.java b/src/main/java/seedu/address/ui/exam/GeneralExamsContent.java new file mode 100644 index 00000000000..19d881b9775 --- /dev/null +++ b/src/main/java/seedu/address/ui/exam/GeneralExamsContent.java @@ -0,0 +1,12 @@ +package seedu.address.ui.exam; + +import seedu.address.ui.detail.DetailedContent; + +/** + * An abstract class that represents the detailed content of a student. + */ +public abstract class GeneralExamsContent extends DetailedContent { + public GeneralExamsContent(String fxml) { + super(fxml); + } +} diff --git a/src/main/java/seedu/address/ui/homework/EmptyHomeworkContent.java b/src/main/java/seedu/address/ui/homework/EmptyHomeworkContent.java new file mode 100644 index 00000000000..7d486932fcb --- /dev/null +++ b/src/main/java/seedu/address/ui/homework/EmptyHomeworkContent.java @@ -0,0 +1,38 @@ +package seedu.address.ui.homework; + +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.student.Student; +import seedu.address.ui.UiUtil; +/** + * An empty UI component that is displayed when there is no homework to be displayed. + */ +public class EmptyHomeworkContent extends GeneralHomeworkContent { + private static final Logger logger = LogsCenter.getLogger(EmptyHomeworkContent.class); + private static final String FXML = "EmptyContent.fxml"; + private static final String MESSAGE = "No homework to display for %s!"; + + @FXML + private Label message; + + /** + * Creates an empty {@code FilledHomeworkContent} with the given {@code Student}. + * + * @param student The student to display the empty homework content for. + */ + public EmptyHomeworkContent(Student student) { + super(FXML); + String textToDisplay = String.format(MESSAGE, student.getName().fullName); + message.setText(UiUtil.addLineBreaksWithoutIndent(textToDisplay, MAX_LINE_LENGTH)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EmptyHomeworkContent // instanceof handles nulls + && message.getText().equals(((EmptyHomeworkContent) other).message.getText())); // state check + } +} diff --git a/src/main/java/seedu/address/ui/homework/FilledHomeworkContent.java b/src/main/java/seedu/address/ui/homework/FilledHomeworkContent.java new file mode 100644 index 00000000000..7dfc1466231 --- /dev/null +++ b/src/main/java/seedu/address/ui/homework/FilledHomeworkContent.java @@ -0,0 +1,58 @@ +package seedu.address.ui.homework; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.chart.PieChart; +import javafx.scene.control.Label; +import javafx.scene.layout.StackPane; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.student.Homework; +import seedu.address.model.student.Student; + +/** + * A UI component that displays detailed information of a {@code Student}. + */ +public class FilledHomeworkContent extends GeneralHomeworkContent { + private static final Logger logger = LogsCenter.getLogger(FilledHomeworkContent.class); + private static final String FXML = "HomeworkContent.fxml"; + private static final String NAME_LABEL = "First Name: %s"; + private static final String LIST_NAME_LABEL = "Homework List: "; + private static final String PIE_CHART_TITLE = "Completed/Pending Homework"; + + @FXML + private Label name; + @FXML + private Label listName; + @FXML + private PieChart homeworkPieChart; + @FXML + private StackPane homeworkListPlaceholder; + + /** + * Creates a {@code FilledHomeworkContent} with the given {@code Student}. + */ + public FilledHomeworkContent(Student student) { + super(FXML); + + name.setText(String.format(NAME_LABEL, student.getName().getFirstName())); + listName.setText(String.format(LIST_NAME_LABEL)); + + // Set the homework pie chart to display the homework data of the student + homeworkPieChart.setData(student.getHomeworkPieChartData()); + homeworkPieChart.setTitle(PIE_CHART_TITLE); + + // Set the homework list panel to display the homework list of the student + ObservableList homeworkList = student.getHomeworkList(); + HomeworkListPanel homeworkListPanel = new HomeworkListPanel(homeworkList); + homeworkListPlaceholder.getChildren().add(homeworkListPanel.getRoot()); + } + + @Override + public boolean equals(Object obj) { + return obj == this // short circuit if same object + || (obj instanceof FilledHomeworkContent // instanceof handles nulls + && name.equals(((FilledHomeworkContent) obj).name)); // state check + } +} diff --git a/src/main/java/seedu/address/ui/homework/GeneralHomeworkContent.java b/src/main/java/seedu/address/ui/homework/GeneralHomeworkContent.java new file mode 100644 index 00000000000..a2b24cd2405 --- /dev/null +++ b/src/main/java/seedu/address/ui/homework/GeneralHomeworkContent.java @@ -0,0 +1,12 @@ +package seedu.address.ui.homework; + +import seedu.address.ui.detail.DetailedContent; + +/** + * An abstract class that represents the detailed content of a student. + */ +public abstract class GeneralHomeworkContent extends DetailedContent { + public GeneralHomeworkContent(String fxml) { + super(fxml); + } +} diff --git a/src/main/java/seedu/address/ui/homework/HomeworkCard.java b/src/main/java/seedu/address/ui/homework/HomeworkCard.java new file mode 100644 index 00000000000..123f6e4eee6 --- /dev/null +++ b/src/main/java/seedu/address/ui/homework/HomeworkCard.java @@ -0,0 +1,68 @@ +package seedu.address.ui.homework; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.student.Homework; +import seedu.address.ui.UiPart; + +/** + * A UI component that displays information of a {@code Person}. + */ +public class HomeworkCard extends UiPart { + private static final String FXML = "HomeworkListCard.fxml"; + private static final String COMPLETED_ICON = "images/completed.png"; + private static final String UNCOMPLETED_ICON = "images/uncompleted.png"; + private static final String DOT = ". "; + private static final String DEADLINE_LABEL = "(by: %s)"; + + @FXML + private HBox cardPane; + @FXML + private Label id; + @FXML + private ImageView statusIcon; + @FXML + private Label description; + @FXML + private Label deadline; + + /** + * Creates a {@code HomeworkCard} with the given {@code Homework} and index to display. + * + * @param homework to be displayed in the card + * @param id of the homework in the list + */ + public HomeworkCard(Homework homework, int id) { + super(FXML); + this.id.setText(id + DOT); + description.setText(homework.getDescription()); + deadline.setText(String.format(DEADLINE_LABEL, homework.getDeadlineString())); + + // Set the status icon to completed or uncompleted + if (homework.isCompleted()) { + statusIcon.setImage(new Image(COMPLETED_ICON)); + } else { + statusIcon.setImage(new Image(UNCOMPLETED_ICON)); + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + // instanceof handles nulls + if (!(other instanceof HomeworkCard)) { + return false; + } + + // state check + HomeworkCard card = (HomeworkCard) other; + return description.getText().equals(card.description.getText()); + } +} diff --git a/src/main/java/seedu/address/ui/homework/HomeworkListPanel.java b/src/main/java/seedu/address/ui/homework/HomeworkListPanel.java new file mode 100644 index 00000000000..e68a27b611e --- /dev/null +++ b/src/main/java/seedu/address/ui/homework/HomeworkListPanel.java @@ -0,0 +1,66 @@ +package seedu.address.ui.homework; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.student.Homework; +import seedu.address.ui.UiPart; + +/** + * Panel containing the list of Homeworks. + */ +public class HomeworkListPanel extends UiPart { + private static final Logger logger = LogsCenter.getLogger(HomeworkListPanel.class); + private static final String FXML = "HomeworkListPanel.fxml"; + + @FXML + private ListView homeworkListView; + + /** + * Creates a {@code HomeworkListPanel} with the given {@code ObservableList}. + */ + public HomeworkListPanel(ObservableList homeworkList) { + super(FXML); + homeworkListView.setItems(homeworkList); + homeworkListView.setCellFactory(listView -> new HomeworkListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Homework} using a {@code HomeworkCard}. + */ + static class HomeworkListViewCell extends ListCell { + @Override + protected void updateItem(Homework homework, boolean empty) { + super.updateItem(homework, empty); + + if (empty || homework == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new HomeworkCard(homework, getIndex() + 1).getRoot()); + } + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof HomeworkListPanel)) { + return false; + } + + // state check + HomeworkListPanel panel = (HomeworkListPanel) other; + return homeworkListView.equals(panel.homeworkListView); + } +} diff --git a/src/main/java/seedu/address/ui/lesson/EmptyLessonsContent.java b/src/main/java/seedu/address/ui/lesson/EmptyLessonsContent.java new file mode 100644 index 00000000000..031dcaf29cb --- /dev/null +++ b/src/main/java/seedu/address/ui/lesson/EmptyLessonsContent.java @@ -0,0 +1,46 @@ +package seedu.address.ui.lesson; + +import java.util.logging.Logger; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.student.Student; +import seedu.address.ui.UiUtil; + +/** + * An empty UI component that is displayed when there is no lessons to be displayed. + */ +public class EmptyLessonsContent extends GeneralLessonsContent { + private static final Logger logger = LogsCenter.getLogger(EmptyLessonsContent.class); + private static final String FXML = "EmptyContent.fxml"; + private static final String MESSAGE = "No lessons to display for %s!"; + + @FXML + private Label message; + + /** + * Creates an empty {@code FilledLessonsContent} with the given {@code Student}. + * + * @param student The student to display the empty exams content for. + */ + public EmptyLessonsContent(Student student) { + super(FXML); + String textToDisplay = String.format(MESSAGE, student.getName().fullName); + message.setText(UiUtil.addLineBreaksWithoutIndent(textToDisplay, MAX_LINE_LENGTH)); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof EmptyLessonsContent)) { + return false; + } + + EmptyLessonsContent other = (EmptyLessonsContent) obj; + return message.getText().equals(other.message.getText()); + } +} diff --git a/src/main/java/seedu/address/ui/lesson/FilledLessonsContent.java b/src/main/java/seedu/address/ui/lesson/FilledLessonsContent.java new file mode 100644 index 00000000000..afa97d8fe21 --- /dev/null +++ b/src/main/java/seedu/address/ui/lesson/FilledLessonsContent.java @@ -0,0 +1,71 @@ +package seedu.address.ui.lesson; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.StackPane; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.student.Lesson; +import seedu.address.model.student.Name; +import seedu.address.model.student.Student; +import seedu.address.ui.homework.FilledHomeworkContent; +import seedu.address.ui.homework.GeneralHomeworkContent; + +/** + * A UI component that displays detailed information of a {@code Student}. + */ +public class FilledLessonsContent extends GeneralHomeworkContent { + private static final Logger logger = LogsCenter.getLogger(FilledHomeworkContent.class); + private static final String FXML = "LessonsContent.fxml"; + private static final String NAME_LABEL = "First Name: %s"; + private static final String PAST_LESSONS_LIST_NAME = "Past Lessons: "; + private static final String UPCOMING_LESSONS_LIST_NAME = "Upcoming Lessons: "; + + private final Name studentName; + + @FXML + private Label name; + @FXML + private Label pastLessonsListName; + + @FXML + private StackPane pastLessonsListPlaceholder; + + @FXML + private Label upcomingLessonsListName; + + @FXML + private StackPane upcomingLessonsListPlaceholder; + + /** + * Creates a {@code FilledHomeworkContent} with the given {@code Student}. + */ + public FilledLessonsContent(Student student) { + super(FXML); + studentName = student.getName(); + + name.setText(String.format(NAME_LABEL, student.getName().getFirstName())); + pastLessonsListName.setText(PAST_LESSONS_LIST_NAME); + + // Set the past lessons list panel to display the past lessons of the student + ObservableList pastLessonsList = student.getPastLessonsList(); + LessonsListPanel pastLessonsListPanel = new LessonsListPanel(pastLessonsList); + pastLessonsListPlaceholder.getChildren().add(pastLessonsListPanel.getRoot()); + + upcomingLessonsListName.setText(UPCOMING_LESSONS_LIST_NAME); + + // Set the upcoming lessons list panel to display the upcoming lessons of the student + ObservableList upcomingLessonsList = student.getUpcomingLessonsList(); + LessonsListPanel upcomingLessonsListPanel = new LessonsListPanel(upcomingLessonsList); + upcomingLessonsListPlaceholder.getChildren().add(upcomingLessonsListPanel.getRoot()); + } + + @Override + public boolean equals(Object obj) { + return obj == this // short circuit if same object + || (obj instanceof FilledLessonsContent // instanceof handles nulls + && studentName.equals(((FilledLessonsContent) obj).studentName)); // state check + } +} diff --git a/src/main/java/seedu/address/ui/lesson/GeneralLessonsContent.java b/src/main/java/seedu/address/ui/lesson/GeneralLessonsContent.java new file mode 100644 index 00000000000..b0c7efb05de --- /dev/null +++ b/src/main/java/seedu/address/ui/lesson/GeneralLessonsContent.java @@ -0,0 +1,12 @@ +package seedu.address.ui.lesson; + +import seedu.address.ui.detail.DetailedContent; + +/** + * An abstract class that represents the detailed content of a student. + */ +public abstract class GeneralLessonsContent extends DetailedContent { + public GeneralLessonsContent(String fxml) { + super(fxml); + } +} diff --git a/src/main/java/seedu/address/ui/lesson/LessonCard.java b/src/main/java/seedu/address/ui/lesson/LessonCard.java new file mode 100644 index 00000000000..151692a5335 --- /dev/null +++ b/src/main/java/seedu/address/ui/lesson/LessonCard.java @@ -0,0 +1,58 @@ +package seedu.address.ui.lesson; + +import java.time.format.DateTimeFormatter; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.student.Lesson; +import seedu.address.ui.UiPart; + +/** + * A UI component that displays information of a {@code Person}. + */ +public class LessonCard extends UiPart { + private static final DateTimeFormatter PRINT_FORMATTER = DateTimeFormatter.ofPattern("dd MMM yyyy HH:mm"); + private static final String FXML = "LessonListCard.fxml"; + private static final String DOT = ". "; + + @FXML + private HBox cardPane; + @FXML + private Label title; + @FXML + private Label startTime; + @FXML + private Label endTime; + + /** + * Creates a {@code LessonCode} with the given {@code Lesson} and index to display. + * + * @param lesson The lesson to be displayed. + * @param id The index of the lesson to be displayed. + */ + public LessonCard(Lesson lesson, int id) { + super(FXML); + title.setText(lesson.getTitle()); + startTime.setText(lesson.getStartTime().format(PRINT_FORMATTER)); + endTime.setText(lesson.getEndTime().format(PRINT_FORMATTER)); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + // instanceof handles nulls + if (!(other instanceof LessonCard)) { + return false; + } + // state check + LessonCard card = (LessonCard) other; + return title.getText().equals(card.title.getText()) + && startTime.getText().equals(card.startTime.getText()) + && endTime.getText().equals(card.endTime.getText()); + } +} diff --git a/src/main/java/seedu/address/ui/lesson/LessonsListPanel.java b/src/main/java/seedu/address/ui/lesson/LessonsListPanel.java new file mode 100644 index 00000000000..a53aaf1e924 --- /dev/null +++ b/src/main/java/seedu/address/ui/lesson/LessonsListPanel.java @@ -0,0 +1,66 @@ +package seedu.address.ui.lesson; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.student.Lesson; +import seedu.address.ui.UiPart; + +/** + * Panel containing the list of Homeworks. + */ +public class LessonsListPanel extends UiPart { + private static final Logger logger = LogsCenter.getLogger(LessonsListPanel.class); + private static final String FXML = "LessonsListPanel.fxml"; + + @FXML + private ListView lessonsListView; + + /** + * Creates a {@code HomeworkListPanel} with the given {@code ObservableList}. + */ + public LessonsListPanel(ObservableList lessonsList) { + super(FXML); + lessonsListView.setItems(lessonsList); + lessonsListView.setCellFactory(listView -> new LessonsListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Homework} using a {@code HomeworkCard}. + */ + static class LessonsListViewCell extends ListCell { + @Override + protected void updateItem(Lesson lesson, boolean empty) { + super.updateItem(lesson, empty); + + if (empty || lesson == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new LessonCard(lesson, getIndex() + 1).getRoot()); + } + } + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof LessonsListPanel)) { + return false; + } + + // state check + LessonsListPanel panel = (LessonsListPanel) other; + return lessonsListView.equals(panel.lessonsListView); + } +} diff --git a/src/main/resources/images/123.png b/src/main/resources/images/123.png new file mode 100644 index 00000000000..29810cf1fd9 Binary files /dev/null and b/src/main/resources/images/123.png differ diff --git a/src/main/resources/images/address_book_32.png b/src/main/resources/images/address_book_32.png index 29810cf1fd9..1e54156c1d8 100644 Binary files a/src/main/resources/images/address_book_32.png and b/src/main/resources/images/address_book_32.png differ diff --git a/src/main/resources/images/completed.png b/src/main/resources/images/completed.png new file mode 100644 index 00000000000..951ed8f33f2 Binary files /dev/null and b/src/main/resources/images/completed.png differ diff --git a/src/main/resources/images/exams_icon.png b/src/main/resources/images/exams_icon.png new file mode 100644 index 00000000000..e8987c0a0cc Binary files /dev/null and b/src/main/resources/images/exams_icon.png differ diff --git a/src/main/resources/images/homework_icon.png b/src/main/resources/images/homework_icon.png new file mode 100644 index 00000000000..c33efa296fb Binary files /dev/null and b/src/main/resources/images/homework_icon.png differ diff --git a/src/main/resources/images/info_icon.png b/src/main/resources/images/info_icon.png index f8cef714095..e980e816d8f 100644 Binary files a/src/main/resources/images/info_icon.png and b/src/main/resources/images/info_icon.png differ diff --git a/src/main/resources/images/lessons_icon.png b/src/main/resources/images/lessons_icon.png new file mode 100644 index 00000000000..f0ae2cbf0bb Binary files /dev/null and b/src/main/resources/images/lessons_icon.png differ diff --git a/src/main/resources/images/profile_icon.png b/src/main/resources/images/profile_icon.png new file mode 100644 index 00000000000..9c0b9dc2a5f Binary files /dev/null and b/src/main/resources/images/profile_icon.png differ diff --git a/src/main/resources/images/uncompleted.png b/src/main/resources/images/uncompleted.png new file mode 100644 index 00000000000..844c8a8c605 Binary files /dev/null and b/src/main/resources/images/uncompleted.png differ diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 09f6d6fe9e4..ff5b1b06eee 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -3,7 +3,6 @@ - + - diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css deleted file mode 100644 index 36e6b001cd8..00000000000 --- a/src/main/resources/view/DarkTheme.css +++ /dev/null @@ -1,352 +0,0 @@ -.background { - -fx-background-color: derive(#1d1d1d, 20%); - background-color: #383838; /* Used in the default.html file */ -} - -.label { - -fx-font-size: 11pt; - -fx-font-family: "Segoe UI Semibold"; - -fx-text-fill: #555555; - -fx-opacity: 0.9; -} - -.label-bright { - -fx-font-size: 11pt; - -fx-font-family: "Segoe UI Semibold"; - -fx-text-fill: white; - -fx-opacity: 1; -} - -.label-header { - -fx-font-size: 32pt; - -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; - -fx-opacity: 1; -} - -.text-field { - -fx-font-size: 12pt; - -fx-font-family: "Segoe UI Semibold"; -} - -.tab-pane { - -fx-padding: 0 0 0 1; -} - -.tab-pane .tab-header-area { - -fx-padding: 0 0 0 0; - -fx-min-height: 0; - -fx-max-height: 0; -} - -.table-view { - -fx-base: #1d1d1d; - -fx-control-inner-background: #1d1d1d; - -fx-background-color: #1d1d1d; - -fx-table-cell-border-color: transparent; - -fx-table-header-border-color: transparent; - -fx-padding: 5; -} - -.table-view .column-header-background { - -fx-background-color: transparent; -} - -.table-view .column-header, .table-view .filler { - -fx-size: 35; - -fx-border-width: 0 0 1 0; - -fx-background-color: transparent; - -fx-border-color: - transparent - transparent - derive(-fx-base, 80%) - transparent; - -fx-border-insets: 0 10 1 0; -} - -.table-view .column-header .label { - -fx-font-size: 20pt; - -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; - -fx-alignment: center-left; - -fx-opacity: 1; -} - -.table-view:focused .table-row-cell:filled:focused:selected { - -fx-background-color: -fx-focus-color; -} - -.split-pane:horizontal .split-pane-divider { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: transparent transparent transparent #4d4d4d; -} - -.split-pane { - -fx-border-radius: 1; - -fx-border-width: 1; - -fx-background-color: derive(#1d1d1d, 20%); -} - -.list-view { - -fx-background-insets: 0; - -fx-padding: 0; - -fx-background-color: derive(#1d1d1d, 20%); -} - -.list-cell { - -fx-label-padding: 0 0 0 0; - -fx-graphic-text-gap : 0; - -fx-padding: 0 0 0 0; -} - -.list-cell:filled:even { - -fx-background-color: #3c3e3f; -} - -.list-cell:filled:odd { - -fx-background-color: #515658; -} - -.list-cell:filled:selected { - -fx-background-color: #424d5f; -} - -.list-cell:filled:selected #cardPane { - -fx-border-color: #3e7b91; - -fx-border-width: 1; -} - -.list-cell .label { - -fx-text-fill: white; -} - -.cell_big_label { - -fx-font-family: "Segoe UI Semibold"; - -fx-font-size: 16px; - -fx-text-fill: #010504; -} - -.cell_small_label { - -fx-font-family: "Segoe UI"; - -fx-font-size: 13px; - -fx-text-fill: #010504; -} - -.stack-pane { - -fx-background-color: derive(#1d1d1d, 20%); -} - -.pane-with-border { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: derive(#1d1d1d, 10%); - -fx-border-top-width: 1px; -} - -.status-bar { - -fx-background-color: derive(#1d1d1d, 30%); -} - -.result-display { - -fx-background-color: transparent; - -fx-font-family: "Segoe UI Light"; - -fx-font-size: 13pt; - -fx-text-fill: white; -} - -.result-display .label { - -fx-text-fill: black !important; -} - -.status-bar .label { - -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; - -fx-padding: 4px; - -fx-pref-height: 30px; -} - -.status-bar-with-border { - -fx-background-color: derive(#1d1d1d, 30%); - -fx-border-color: derive(#1d1d1d, 25%); - -fx-border-width: 1px; -} - -.status-bar-with-border .label { - -fx-text-fill: white; -} - -.grid-pane { - -fx-background-color: derive(#1d1d1d, 30%); - -fx-border-color: derive(#1d1d1d, 30%); - -fx-border-width: 1px; -} - -.grid-pane .stack-pane { - -fx-background-color: derive(#1d1d1d, 30%); -} - -.context-menu { - -fx-background-color: derive(#1d1d1d, 50%); -} - -.context-menu .label { - -fx-text-fill: white; -} - -.menu-bar { - -fx-background-color: derive(#1d1d1d, 20%); -} - -.menu-bar .label { - -fx-font-size: 14pt; - -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; - -fx-opacity: 0.9; -} - -.menu .left-container { - -fx-background-color: black; -} - -/* - * Metro style Push Button - * Author: Pedro Duque Vieira - * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ - */ -.button { - -fx-padding: 5 22 5 22; - -fx-border-color: #e2e2e2; - -fx-border-width: 2; - -fx-background-radius: 0; - -fx-background-color: #1d1d1d; - -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; - -fx-font-size: 11pt; - -fx-text-fill: #d8d8d8; - -fx-background-insets: 0 0 0 0, 0, 1, 2; -} - -.button:hover { - -fx-background-color: #3a3a3a; -} - -.button:pressed, .button:default:hover:pressed { - -fx-background-color: white; - -fx-text-fill: #1d1d1d; -} - -.button:focused { - -fx-border-color: white, white; - -fx-border-width: 1, 1; - -fx-border-style: solid, segments(1, 1); - -fx-border-radius: 0, 0; - -fx-border-insets: 1 1 1 1, 0; -} - -.button:disabled, .button:default:disabled { - -fx-opacity: 0.4; - -fx-background-color: #1d1d1d; - -fx-text-fill: white; -} - -.button:default { - -fx-background-color: -fx-focus-color; - -fx-text-fill: #ffffff; -} - -.button:default:hover { - -fx-background-color: derive(-fx-focus-color, 30%); -} - -.dialog-pane { - -fx-background-color: #1d1d1d; -} - -.dialog-pane > *.button-bar > *.container { - -fx-background-color: #1d1d1d; -} - -.dialog-pane > *.label.content { - -fx-font-size: 14px; - -fx-font-weight: bold; - -fx-text-fill: white; -} - -.dialog-pane:header *.header-panel { - -fx-background-color: derive(#1d1d1d, 25%); -} - -.dialog-pane:header *.header-panel *.label { - -fx-font-size: 18px; - -fx-font-style: italic; - -fx-fill: white; - -fx-text-fill: white; -} - -.scroll-bar { - -fx-background-color: derive(#1d1d1d, 20%); -} - -.scroll-bar .thumb { - -fx-background-color: derive(#1d1d1d, 50%); - -fx-background-insets: 3; -} - -.scroll-bar .increment-button, .scroll-bar .decrement-button { - -fx-background-color: transparent; - -fx-padding: 0 0 0 0; -} - -.scroll-bar .increment-arrow, .scroll-bar .decrement-arrow { - -fx-shape: " "; -} - -.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow { - -fx-padding: 1 8 1 8; -} - -.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow { - -fx-padding: 8 1 8 1; -} - -#cardPane { - -fx-background-color: transparent; - -fx-border-width: 0; -} - -#commandTypeLabel { - -fx-font-size: 11px; - -fx-text-fill: #F70D1A; -} - -#commandTextField { - -fx-background-color: transparent #383838 transparent #383838; - -fx-background-insets: 0; - -fx-border-color: #383838 #383838 #ffffff #383838; - -fx-border-insets: 0; - -fx-border-width: 1; - -fx-font-family: "Segoe UI Light"; - -fx-font-size: 13pt; - -fx-text-fill: white; -} - -#filterField, #personListPanel, #personWebpage { - -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); -} - -#resultDisplay .content { - -fx-background-color: transparent, #383838, transparent, #383838; - -fx-background-radius: 0; -} - -#tags { - -fx-hgap: 7; - -fx-vgap: 3; -} - -#tags .label { - -fx-text-fill: white; - -fx-background-color: #3e7b91; - -fx-padding: 1 3 1 3; - -fx-border-radius: 2; - -fx-background-radius: 2; - -fx-font-size: 11; -} diff --git a/src/main/resources/view/DetailedInfoRegion.fxml b/src/main/resources/view/DetailedInfoRegion.fxml new file mode 100644 index 00000000000..7a6f6a0da90 --- /dev/null +++ b/src/main/resources/view/DetailedInfoRegion.fxml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/main/resources/view/EmptyContent.fxml b/src/main/resources/view/EmptyContent.fxml new file mode 100644 index 00000000000..f7ba09a962c --- /dev/null +++ b/src/main/resources/view/EmptyContent.fxml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/src/main/resources/view/ExamListCard.fxml b/src/main/resources/view/ExamListCard.fxml new file mode 100644 index 00000000000..473c24c7427 --- /dev/null +++ b/src/main/resources/view/ExamListCard.fxml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/ExamsContent.fxml b/src/main/resources/view/ExamsContent.fxml new file mode 100644 index 00000000000..d7927415d0e --- /dev/null +++ b/src/main/resources/view/ExamsContent.fxml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/ExamsListPanel.fxml b/src/main/resources/view/ExamsListPanel.fxml new file mode 100644 index 00000000000..3ac3423be3b --- /dev/null +++ b/src/main/resources/view/ExamsListPanel.fxml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/HeaderBar.fxml b/src/main/resources/view/HeaderBar.fxml new file mode 100644 index 00000000000..ccad0dcb891 --- /dev/null +++ b/src/main/resources/view/HeaderBar.fxml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/HelpWindow.css b/src/main/resources/view/HelpWindow.css deleted file mode 100644 index 17e8a8722cd..00000000000 --- a/src/main/resources/view/HelpWindow.css +++ /dev/null @@ -1,19 +0,0 @@ -#copyButton, #helpMessage { - -fx-text-fill: white; -} - -#copyButton { - -fx-background-color: dimgray; -} - -#copyButton:hover { - -fx-background-color: gray; -} - -#copyButton:armed { - -fx-background-color: darkgray; -} - -#helpMessageContainer { - -fx-background-color: derive(#1d1d1d, 20%); -} diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml index 5dea0adef70..5c029cb55f7 100644 --- a/src/main/resources/view/HelpWindow.fxml +++ b/src/main/resources/view/HelpWindow.fxml @@ -9,16 +9,16 @@ - + - + -