diff --git a/LICENSE b/LICENSE index 39b3478982c..3ee16000e96 100644 --- a/LICENSE +++ b/LICENSE @@ -2,11 +2,11 @@ MIT License Copyright (c) 2016 Software Engineering Education - FOSS Resources -Permission is hereby granted, free of charge, to any person obtaining a copy +Permission is hereby granted, free of charge, to any student obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is +copies of the Software, and to permit students to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all diff --git a/README.md b/README.md index 13f5c77403f..bcd9982fe23 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,17 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) +# Teaching Assistant Assistant (TAA) + +[![CI Status](https://github.com/AY2223S1-CS2103T-T13-1/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2223S1-CS2103T-T13-1/tp/actions) + +Teaching Assistant Assistant (TAA) is a desktop app for Teaching Assistants (TAs). +It keeps track of TAs’ students, tutorial groups, and tasks. ![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)**. -* 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. +## More information + +- You can find the User Guide and more information [here](https://ay2223s1-cs2103t-t13-1.github.io/tp/). + +## Acknowledgements + +This project is based on the AddressBook-Level3 project +created by the [SE-EDU initiative](https://se-education.org). diff --git a/build.gradle b/build.gradle index 108397716bd..9e2152da251 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,10 @@ repositories { maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } } +run { + enableAssertions = true; +} + checkstyle { toolVersion = '10.2' } @@ -61,6 +65,7 @@ dependencies { implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.7.4' testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: jUnitVersion + testImplementation "org.mockito:mockito-core:3.+" testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: jUnitVersion } diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..4162425d3f7 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -9,51 +9,51 @@ You can reach us at the email `seer[at]comp.nus.edu.sg` ## Project team -### John Doe +### Lim Wei Jun - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/limweijun)] +[[portfolio](team/limweijun.md)] -* Role: Project Advisor +- Role: Developer +- Responsibilities: CI/CD, Testing -### Jane Doe +### Lam Chun Yu - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/gunbux)] [[portfolio](team/gunbux.md)] -* Role: Team Lead -* Responsibilities: UI +- Role: Developer +- Responsibilities: Big Architectural Planning, UML Diagrams -### Johnny Doe +### Lu Yiting - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](https://github.com/LuYiting0913)] +[[portfolio](team/luyiting0913.md)] -* Role: Developer -* Responsibilities: Data +- Role: Developer +- Responsibilities: User Stories Planning -### Jean Doe +### Yeo Jun Jie - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/yeojunjie)] +[[portfolio](team/yeojunjie.md)] -* Role: Developer -* Responsibilities: Dev Ops + Threading +- Role: Developer +- Responsibilities: UI Specialist -### James Doe +### Bag Devesh Kumar - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/tensaida)] +[[portfolio](team/tensaida.md)] -* Role: Developer -* Responsibilities: UI +- Role: Developer +- Responsibilities: Command Line Specialist diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 46eae8ee565..289d892379a 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -2,64 +2,72 @@ layout: page title: Developer Guide --- + +## Welcome to the TAA Developer Guide! +*Teaching Assistant Assistant (TAA)* is a **desktop app for Teaching Assistants (TA) to track student progress and tasks, +optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). + +This developer guide serves the purpose of helping developers to quickly gain the knowledge they need to start contributing in developing TAA. + * Table of Contents {:toc} --------------------------------------------------------------------------------------------------------------------- +--- ## **Acknowledgements** -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +- Mockito for mocking objects in tests --------------------------------------------------------------------------------------------------------------------- +--- ## **Setting up, getting started** Refer to the guide [_Setting up and getting started_](SettingUp.md). --------------------------------------------------------------------------------------------------------------------- +--- ## **Design**
-: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/AY2223S1-CS2103T-T13-1/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 -The ***Architecture Diagram*** given above explains the high-level design of the App. +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. **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, -* 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. +**`Main`** has two classes called [`Main`](https://github.com/AY2223S1-CS2103T-T13-1/tp/blob/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/AY2223S1-CS2103T-T13-1/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. [**`Commons`**](#common-classes) represents a collection of classes used by multiple other components. The rest of the App consists of four components. -* [**`UI`**](#ui-component): The UI of the App. -* [**`Logic`**](#logic-component): The command executor. -* [**`Model`**](#model-component): Holds the data of the App in memory. -* [**`Storage`**](#storage-component): Reads data from, and writes data to, the hard disk. - +- [**`UI`**](#ui-component): The UI of the App. +- [**`Logic`**](#logic-component): The command executor. +- [**`Model`**](#model-component): Holds the data of the App in memory. +- [**`Storage`**](#storage-component): Reads data from, and writes data to, the hard disk. **How the architecture components interact with each other** -The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. +The _Sequence Diagram_ below shows how the components interact with each other for the scenario where the user issues the command `student delete 1`. - +![ArchitectureSequenceDiagram](images/ArchitectureSequenceDiagram.png) Each of the four main components (also shown in the diagram above), -* defines its *API* in an `interface` with the same name as the Component. -* implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point. +- defines its _API_ in an `interface` with the same name as the Component. +- implements its functionality using a concrete `{Component Name}Manager` class (which follows the corresponding API `interface` mentioned in the previous point. For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality using the `LogicManager.java` class which follows the `Logic` interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below. @@ -69,36 +77,39 @@ 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`](hhttps://github.com/AY2223S1-CS2103T-T13-1/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 such as `CommandOutput`, `CommandInput`, `StudentListPanel`, and `TaskListPanel`. 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. 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`. +- 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 `Student` objects and `Task` objects 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/AY2223S1-CS2103T-T13-1/tp/blob/master/src/main/java/seedu/address/logic/Logic.java) 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`. -The Sequence Diagram below illustrates the interactions within the `Logic` component for the `execute("delete 1")` API call. +1. When `Logic` is called upon to execute a command, it uses the `TaaParser` class to parse the user command. +2. This results in a `Command` object which is executed by the `LogicManager`. + * If the command takes in no arguments, the `Command` is directly created. + * If the command takes in arguments, a parser is created to create the `Command`. +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 back from `Logic`. + +The Sequence Diagram below illustrates the interactions within the `Logic` component for the `execute("student delete 1")` API call. ![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png) @@ -110,146 +121,356 @@ Here are the other classes in `Logic` (omitted from the class diagram above) tha How the parsing works: -* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object. -* 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. +- Let `ABC` represent the name of a command that takes in at least one argument. Examples of `ABC` include `TaskDelete` and `TutorialGroupAdd`. +- When called upon to parse a user command, the `TaaParser` class creates an `ABCCommandParser` that uses the other classes shown above to parse the user command to create an `ABCCommand` object (e.g., `TaskDeleteCommand`) which the `TaaParser` returns back as a `Command` object. +- All `ABCCommandParser` classes 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/AY2223S1-CS2103T-T13-1/tp/blob/master/src/main/java/seedu/address/model/Model.java) +![ModelClassDiagram](images/ModelClassDiagram.png) 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 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.
- - - -
- +- 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. + - The same configuration applies to `Task` and `TutorialGroup` objects. +- stores the `GradeMap` which maps a unique `GradeKey` (composed of a `Student` and `Task` object) to a `Grade` object +- 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) ### 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/AY2223S1-CS2103T-T13-1/tp/blob/master/src/main/java/seedu/address/storage/Storage.java) - +![StorageClassDiagram](images/StorageClassDiagram.png) The `Storage` component, -* can save both address book data and user preference data in json format, and read them back into corresponding objects. -* inherits from both `AddressBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed). -* depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`) + +- can save both address book data and user preference data in json format, and read them back into corresponding objects. +- inherits from both `AddressBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed). +- depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`) ### Common classes Classes used by multiple components are in the `seedu.addressbook.commons` package. --------------------------------------------------------------------------------------------------------------------- +--- ## **Implementation** This section describes some noteworthy details on how certain features are implemented. -### \[Proposed\] Undo/redo feature +### Summary of Implementations + +| Feature | Description | +|------------------------------------------------------------|-----------------------------------------------------------------| +| **[Tutorial Group](#tutorial-group-feature)** | Allows the user to add tutorial groups to students | +| **[Task](#task-feature)** | Allows the user to add tasks to students | +| **[Grade](#grade-feature)** | Allows the user to add grades to students | +| **[Task Sorting](#sort-task-by-deadline-feature)** | Allows the user to sort tasks by deadline | +| **[Mass Actions](#mass-actions-feature)** | Allows the user to perform actions on multiple students at once | +| **[Expanding Task List](#expanding-tasklistcard-feature)** | Allows the user to expand the task list to see more details | -#### Proposed Implementation +### Tutorial group feature -The proposed undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. Additionally, it implements the following operations: +#### Implementation -* `VersionedAddressBook#commit()` — Saves the current address book state in its history. -* `VersionedAddressBook#undo()` — Restores the previous address book state from its history. -* `VersionedAddressBook#redo()` — Restores a previously undone address book state from its history. +The tutorial group feature is facilitated by `TutorialGroup`. It implements the following operations: -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +- `TutorialGroup#TutorialGroup(String)` — Create a tutorial group with the name provided. The name must follow a format of "TXX". +- `TutorialGroup#isSameTutorialGroup()` — Check whether the two tutorial groups have the same name. +- `TutorialGroup#getStudents()` — Gets a list of students belong to this tutorial group. Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. -Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. +Step 1. The user launches the application for the first time. -![UndoRedoState0](images/UndoRedoState0.png) +Step 2. The user executes `tutorialList` command to display all the tutorial groups. -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 3. The user executes `tutorialAdd g/T03` command to add a new tutorial group. -![UndoRedoState1](images/UndoRedoState1.png) +Step 4. The user executes `studentEdit 1 g/T03` command to assign the first student to the newly created tutorial group. -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`. +#### Design Considerations -![UndoRedoState2](images/UndoRedoState2.png) +The implementation for tutorial group is quite similar to what was done for the base AB3. -
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`. +Show below is an activity diagram of how a tutorial group is added. -
+![Tutorial Add Activity Diagram](images/TutorialAddActivityDiagram.png) -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. +However, the main difference comes with the ability to enroll a student in a tutorial group. In `TutorialGroup`, we had to +use a list of students to represent the students enrolled in this tutorial group. +However, we couldn't create new Student and TutorialGroup when we enroll a student. +Therefore, we decided to use a isSameTutorialGroup method to look for the target tutorial group, and update the tutorial +group field of the target student. -![UndoRedoState3](images/UndoRedoState3.png) +Show below is an activity diagram of how a student is enrolled in a tutorial group. -
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather -than attempting to perform the undo. +![Student Enroll Activity Diagram](images/StudentEnrollActivityDiagram.png) -
+### Task feature -The following sequence diagram shows how the undo operation works: +#### Description -![UndoSequenceDiagram](images/UndoSequenceDiagram.png) +The task features is an extension on AB3 to add a list of tasks to the `Model` that we then use to track different +tasks that the user has to complete. -
:information_source: **Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. +#### Implementation -
+The task feature is facilitated by `Task`. It implements the following operations: +* Adding Tasks using a Name, Description, Deadline and assigned students (Optional) +* Deleting Tasks using the Task's index +* Editing Tasks using the Task's index +* Marking Tasks as done using the Task's index (In Progress) +* Viewing Tasks -The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state. +Below is the class diagram for Task. + -
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo. +#### Design Considerations -
+The implementation is quite similar to what was done for the base AB3, as well as +the tutorial group and student feature. However, there were a few differences in the +implementation that we had to take note of. + +The main issue comes with the ability to edit tasks. In `TutorialGroup` and `Student`, +the implementation of the edit command was quite simple, which was to have a class to store all the changes +and merge them with the states in the `Model`. However, in `Task`, the implementation was a bit more complex, +as we had to take into account the students assigned to the task. We couldn't create new Students and then edit +the task, since the students would be new and not in the `Model` yet. Thus, we had to use a different method, +instead opting to defer the creation of the new `Task` to the `Model` itself. This was done by rewriting the +code to parse in all the fields of `Task` and leaving student as a list of Strings, which we then use to search through +the `Model` to find the students that are assigned to the task. + +Show below is an activity diagram of how a task is edited. + +![Task Edit Activity Diagram](images/TaskEditActivityDiagram.png) + +### Grade feature + +#### Description + +In TAA, the user can specify whether they have graded a `Student`'s `Task` or not. + +#### Implementation + +##### Model + +The model component contains a `GradeMap`, which maps a `GradeKey` object to a `Grade` enum. + + + +
:information_source: **Note:** The association between `GradeKey` and `Key` is a *qualified association*: any given `GradeKey` object will be associated with at least and at most one `Grade`, but due to a limitation of PlantUML, the correct UML notation for a qualified association has not been displayed. +
+ + + +Since a `Grade` object is associated with a `Student` and `Task` pair, `GradeKey` consists of a `Student` field and a `Task` field. + +Therefore, editing the `Task` or `Student` using `task edit` or `student edit` necessitates updating the `GradeMap` as well, since the `edit` commands generate new `Task` and `Student` objects. +Given below is a sequence diagram illustrating this when the user uses the `task edit 1 tn/hi` command. + + + +##### Commands + +###### Viewing the grade with `grade view` + +The sequence diagram below illustrates the interactions within the `Logic` component for the `execute("grade view 1 2")` API call, where `1` is the student index and `2` is the task index. -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. + -![UndoRedoState4](images/UndoRedoState4.png) +
:information_source: **Note:** The lifeline for `GradeViewCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches +the end of diagram.
-Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …​` command. This is the behavior that most modern desktop applications follow. +The following activity diagram summarizes what happens when the user executes this command: -![UndoRedoState5](images/UndoRedoState5.png) + -The following activity diagram summarizes what happens when a user executes a new command: +###### Editing the grade with `grade edit` - +The sequence diagram below illustrates the interactions within the `Logic` component for the `execute("grade view 1 2 gr/T")` API call, where `1` is the student index and `2` is the task index. + + + +
:information_source: **Note:** The lifeline for `GradeEditCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches +the end of diagram.
+ +The following activity diagram summarizes what happens when the user executes this command: + + + +#### Design Considerations + +**Aspect: Representation of a grade** + +- **Alternative 1 (current choice):** `Grade` is an enum. + + - Pros: + - Easier and faster to implement than making `Grade` a full-fledged class. + - `Grade`s are a group of constants, so making `Grade` an enum is natural. + - While for now only two grades are supported ("graded" and "ungraded"), in the future, more grades can easily be added in the enum. + - Cons: + - In the future, if operations are to be done on `Grade`, then it is better to switch to implement as a class rather than an enum. + + +- **Alternative 2:** `Grade` is a boolean. + + - Pros: + - Easier and faster to implement than making `Grade` an enum. + - Uses less memory since a boolean is a primitive type which can be either `true` or `false`. + - Cons: + - More grades cannot be added easily in the future. A switch to an enum or a class must be made in that case. + +- **Alternative 3:** `Grade` is a class + + - Pros: + - Extremely customisable. Any `String` can be encapsulated inside a `Grade` class. + - Instance methods can prove to be useful and make codebase more cohesive by grouping `Grade` related methods into the `Grade` class. + - Cons: + - Harder to test + - Most memory-intensive + - Slowest to implement + +In the interest of future extensibility and fast implementation, `Grade` was made an enum. + +**Aspect: Choice of collection for `GradeMap`** + +- **Alternative 1 (current choice):** Use a `HashMap`. + + - Pros: O(1) lookups in `HashMap` + - Cons: + - Provides only methods for lookup and removal of entries. + - A `HashMap` does not maintain order of the entries. + +- **Alternative 2:** Use a `TreeMap`. + + - Pros: `TreeMap` provides methods like `lowerKey()` and `higherKey()` that can be used to answer queries that are more complicated than lookups. + - Cons: Slower, `O(log n)` lookups + +- **Alternative 3:** Use an `ArrayList`. + + - Pros: + - Can support a wider variety of queries than a `TreeMap` + - Has a corresponding `FilteredList` that can be used to highlight results of a query in a way that is faster to implement. + - Cons: + - Very slow, `O(n)` lookup. This can be especially problematic when there are a large number of `Student`s and `Task`s. + - Cumbersome implementation. + +The `HashMap` Java Collection was chosen because of faster lookups, since in the current iteration of TAA, the `GradeMap` is only used to add and retrieve grades. However, in the future, `GradeMap` can be changed easily to use a `TreeMap` instead. A `TreeMap` implementation would prove useful if more than 2 grades are added and a filter feature is desired, for example, get a list of `Student`s who scored more than a `B` in a particular `Task`. `ArrayList` was rejected because it is cumbersome to implement and is not as performant. + +### Sort task by deadline feature + +#### Implementation + +The sort task by deadline mechanism will be implemented without the need to enter additional commands. + +Additionally, its feature is exposed in the `List` interface by calling its `sort` method. + +The sort method wil automatically sort its tasks by ascending order whenever a new task is added or an existing task is edited. + +Given below is an example usage scenario of adding a new task and how the task sorted by deadline mechanism behaves. + +Step 1. The user launches the application and enters the respective `task add` command. + +Step 2. If the command is successful, a `task` object is created and will eventually be passed to `UniqueTaskList`. + +Step 3. `UniqueTaskList` will add the `Task` object to an object of type `ObservableList` + +Step 4. `UniqueTaskList` will then call its own method `sortByDeadline` + +Step 5. `sortByDeadline` will call the `sort` method of the same object of type `ObservableList` from Step 5. + +Step 6. The `sort` method takes in an object of type Comparator which compares two different `Task` objects' based on its deadline variable. + +Step 7. The `sort` method then iterate the object of type `ObservableList` and arrange them according to their deadline in ascending order. + +The _Sequence Diagram_ below shows how the components interact with each other for the scenario during task sort when a user adds a new task. Similar interactions take place when a user edits a task or removes a task. + + #### Design considerations: -**Aspect: How undo & redo executes:** +**Aspect: How sorting task by deadline executes:** + +- **Alternative 1 (current choice):** sort tasks by default. + + - Pros: Easy to implement. + - Cons: Might not be ideal if users do not want it to be sorted. + +- **Alternative 2:** Individual command to sort tasks. + - Pros: User has a choice whether they want their list to be sorted. + - Cons: More classes to implement e.g. `taskSortCommand.java` and `taskSortCommandParser.java`. + +### Mass Actions feature -* **Alternative 1 (current choice):** Saves the entire address book. - * Pros: Easy to implement. - * Cons: May have performance issues in terms of memory usage. +#### Description +The idea behind Mass Actions is to be able to chain together multiple commands without having to type them out one +by one. This is useful for when the user wants to perform the same action on multiple students or tutorial groups. -* **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. +#### Implementation +The mass actions feature requires a rework of the parsers, particularly StudentDeleteCommandParser +and TaskDeleteCommandParser. Instead of parsing a single index, the parsers will parse a range of indices, +then loop through each index, getting the task before deleting it. -_{more aspects and alternatives to be added}_ +#### Design Considerations +The implementation is different from the original AB3 implementation, as there were some special considerations +to keep in mind. The key issue was that we had to do two separate loops, one for getting the list of tasks/students +to delete, before then proceeding to delete them. This is because if we attempt to do it in a single loop, we +encounter an error where the list of tasks/students is modified while we are iterating through it. -### \[Proposed\] Data archiving +#### Alternative Considerations +Another way of doing this instead of completely reworking the commands would be to overload the constructor, +however, it seemingly would not be as clean as the current implementation, as the current implementation is able +to handle one or more indices, while the alternative implementation would be doing double work to cover the case with +one index. -_{Explain here how the data archiving feature will be implemented}_ +### Expanding `TaskListCard` feature +#### Description --------------------------------------------------------------------------------------------------------------------- +In TAA, the user can specify which `Student`s a `Task` has to be completed for. In the UI, each `Task` is displayed as a `TaskListCard`. The `TaskListCard` can be clicked to show or hide the `Student`s. +The part of the UI highlighted in blue and grey are the `TaskListCard`s. The blue card has been clicked to show the `Student`s, while the grey card has a collapsed `Student` list. + + + +#### Implementation + +Every `TaskListCard` contains: +* a `VBox optionalInfo` UI component which displays the students' names to the user, +* a `boolean isExpanded` attribute to determine whether to show the `optionalInfo`, and +* a `void onCardClicked()` method to toggle `isExpanded`. + +#### Design Considerations + +If a `Task` has no `Student`s, +* clicking on its `TaskListCard` does nothing, and +* `"No students are assigned to this task."` is displayed to the user. + +#### Alternative Designs Considered + +1. **Always show the students for all tasks.** This is rejected because tutorial groups contain many students. This allows very few tasks to be displayed on the UI at one time. This prevents the user from having an at-a-glance view of their tasks. +2. **Display the students for a task in a pop-up dialog box.** This is rejected because we wish to minimise the number of mouse clicks needed to switch from viewing the students under Task 1 to viewing the students under Task 2. + * In the current design, the user only needs to click once on Task 2 to collapse Task 1 and expand Task 2. + * This alternative design requires the user to click on the **Close** button on Task 1's dialog box, and then click again on Task 2. + +Here's an activity diagram to demonstrate the Expanding TaskListCard feature. + + +--- ## **Documentation, logging, testing, configuration, dev-ops** -* [Documentation guide](Documentation.md) -* [Testing guide](Testing.md) -* [Logging guide](Logging.md) -* [Configuration guide](Configuration.md) -* [DevOps guide](DevOps.md) +- [Documentation guide](Documentation.md) +- [Testing guide](Testing.md) +- [Logging guide](Logging.md) +- [Configuration guide](Configuration.md) +- [DevOps guide](DevOps.md) --------------------------------------------------------------------------------------------------------------------- +--- ## **Appendix: Requirements** @@ -257,73 +478,195 @@ _{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 +- is a SoC student TA who wants to manage a lot of tasks and students in their care +- has a need to manage different students of varying learning speeds +- needs a way to mass manage all their students and respective tasks +- prefer desktop apps over other types +- can type fast +- prefers typing to mouse interactions +- is reasonably comfortable using CLI apps -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app +**Value proposition**: +- manage contacts faster than a typical mouse/GUI driven app +- a way for student TAs to efficiently keep track of students' progress and tasks ### User stories Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` -| 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 | - -*{More to be added}* +| 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 | +| `* * *` | Teaching Assistant | be able to manage (CRUD) a list of my students | find and manage my students | +| `* * *` | Teaching Assistant | be able to manage (CRUD) any student’s task list | find and manage my tasks | +| `* * *` | Teaching Assistant | keep track of which students’ assignments I have graded and not graded | can find the ungraded assignments faster | +| `* * *` | Teaching Assistant with multiple tutorial groups | keep track of which student is in which group | manage students based on their tutorial groups | +| `* * *` | Teaching Assistant | be able to assign deadlines to my tasks | keep track of the upcoming tasks easily | +| `* * *` | Teaching Assistant | a way to do mass actions | can save time | +| `* *` | Teaching Assistant | leave comments on students’ submissions | can help them learn from their mistakes | +| `* *` | busy Teaching Assistant | want to prioritise tasks based on their deadline | can avoid missing deadlines | ### Use cases -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) +(For all use cases below, the **System** is the `TAA` and the **User** is the `teaching assistant`, unless specified otherwise) -**Use case: Delete a person** +**Use case UC1: Add 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 chooses to add a student record. +2. TAA displays the page for adding student. +3. User inputs the details of the student. +4. User confirms the details. +5. TAA add the record to the system. - Use case ends. + Use case ends. **Extensions** -* 2a. The list is empty. +- 3a. TAA checks for duplicates and incorrect formating. - Use case ends. +- 3a1. TAA requests for correct data and format. -* 3a. The given index is invalid. +- 3a2. User inputs the correct data. - * 3a1. AddressBook shows an error message. +- Steps 3a1-3a2 are repeated until the data entered are correct. - Use case resumes at step 2. + Use case resumes from step 4. -*{More to be added}* +**Use case UC2: Edit a student record** + +**MSS** + +1. User searches for his/her name or ID. +2. TAA displays all the students records with a matching signature. +3. User chooses the correct student. +4. User edit the student record. +5. User confirms the update. +6. TAA saves the new student record. + + Use case ends. + +**Extensions** + +- 2a. TAA cannot find any student record with a matching signature. + +- 2b. TAA requests for re-enter. + +- 2c. User re-enter the student name or ID + + Use case resume from step 3. + +**Use case UC3: Tag a student who has not finished his/her assignments** + +**MSS** + +1. User searches for his/her name or ID. +2. TAA displays all the students records with a matching signature. +3. User chooses the correct student. +4. User chooses the type of tag. +5. User confirms the tagging action. +6. TAA register the tag and update accordingly. + + Use case ends. + + +**Use case UC4: Enroll a student to a tutorial group** + +**MSS** + +1. User searches for his/her name or ID. +2. TAA displays all the students records with a matching signature. +3. User chooses the correct student. +4. User searches for the name of the tutorial group. +5. TAA displays all the tutorial groups. +6. User chooses the correct tutorial group. +7. User confirms the enrollment. +8. TAA enrolls the student to the tutorial group. + + Use case ends. + +**Extensions** + +- 2a. The inputted student index is out of range. + +- 2b. TAA displays error and requests re-enter of the student index. + + Use case resume from step 4. -### 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. -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. +- 7a. The student is already enrolled in a tutorial group. -*{More to be added}* +- 7b. TAA displays error and requests the student to be expelled from his/her current tutorial group. + +- 7c. User expels the student from his/her current tutorial group. + + Use case resume from step 1. + + +**Use case UC5: Expel a student from a tutorial group** + +**MSS** + +1. User searches for the student's name or ID. +2. TAA displays all the students records with a matching signature. +3. User chooses the correct student. +4. User confirms to expel the student from his/her current tutorial group. +5. TAA expels the student from the tutorial group. + + Use case ends. + +**Extensions** + +- 3a. The student is not enrolled in any tutorial groups. + +- 3b. TAA displays error and requests re-enter of the student index. + + Use case resume from step 3. + +**Use case UC6: Add a new tutorial group** + +**MSS** + +1. User chooses to add a new tutorial group. +2. TAA adds a tutorial group with the given name. + + Use case ends. + +**Extensions** + +- 1a. There already exists a tutorial group with the same name. + +- 1b. TAA requests re-enter of the tutorial group name. + + Use case resume from step 2. + +### 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 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. The system should respond within two seconds +5. The system should be usable by someone who's tech illiterate +6. The system should work on both 32-bit and 64-bit environments +7. The system will need at least 100MB of disk space to run the application +8. Teaching Assistant Assistant should give constructive feedback for invalid command +9. Teaching Assistant Assistant should be able to work in an offline setting ### Glossary -* **Mainstream OS**: Windows, Linux, Unix, OS-X -* **Private contact detail**: A contact detail that is not meant to be shared with others +- **Mainstream OS**: Windows, Linux, Unix, MacOS +- **TA**: Teaching Assistant, a student who helps the lecturer in a class +- **CRUD**: Create, Read, Update, Delete. The four main functions that are implemented by a persistent storage application. +- **SoC**: School of Computing, National University of Singapore +- **CLI**: Command Line Interface +- **GUI**: Graphical User Interface +- **32-bit/64-bit**: The number of bits that a computer's CPU (Central Processing Unit) can process at one time. 32-bit CPUs can only process 32 bits of data at one time, while 64-bit CPUs can process 64 bits of data at one time. + 64-bit CPUs are more powerful than 32-bit CPUs, but 32-bit CPUs are still more common in computers. +- **Disk Space**: The amount of space available on a computer's hard disk for storing data. --------------------------------------------------------------------------------------------------------------------- +--- ## **Appendix: Instructions for manual testing** @@ -347,31 +690,95 @@ testers are expected to do more *exploratory* testing. 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 person - -1. Deleting a person while all persons are being shown - - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. - - 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. - - 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)
- 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}_ + Expected: The most recent window size and location is retained. + +### Using the application + +#### Student management +1. Creating, removing, and viewing students + + 1. Create a student with the name "John Doe" using `student add n/John Doe e/abc@example.com p/91234567`.
+ Expected: The UI is updated with the new student list. + 2. Edit the student with the command `student edit 4 p/97654321` + 3. Delete the student with the command `student delete 4` + +#### Task management +1. Creating, removing, grading and viewing tasks + + 1. Create a student with the name "Assignment 1" using `task add tn/Assignement 1 i/Complete Assignment 1 d/10/10/2022`.
+ 2. Click on the `Tasks` Button on the left side of the screen.
+ Expected: The UI is updated with the new task list. + 3. Edit the student with the command `task edit 1 s/David Li` + 4. Double click on the task to view the task details. + Expected: The details shows the list of students assigned to the task. + 5. Grade the student with the command `grade edit 1 1 gr/T` + 6. Change the view back the students, then switch back to Tasks view, the task should be updated with the new grade.
+**:information_source: This is a known bug with JavaFX**
+ 7. Delete the student with the command `task delete 1` + +#### Tutorial group management +1. Creating and managing tutorial groups + 1. Create a new tutorial group with the format TXX. `tutorial add g/T01` + 2. See a list of tutorial groups using `tutorial list`
+ Expected: A list of tutorial groups is shown. + 3. Add a student to the tutorial group with the format `student enroll 1 g/T01` + 4. The UI should be updated with the new student list with update tutorial group info. + 5. Filter the student list by tutorial group with the format `tutorial filter g/T01` + 6. Clear the filter with the format `student unfilter` + +### General Use Cases + +#### Help +1. Type `help me` for a link to the user guide.
+ Expected: A new window is opened with the user guide link. + +#### Exiting the program +1. Type `bye bye`
+ Expected: The application exits. Data is automatically saved. + +## Appendix: Effort + +### Difficulty Level + +
+If the implementation effort required to create AB3 from scratch is 10, the estimated +implementation effort of this team is, [0..20] e.g., if you give 8, that means the team's +effort is about 80% of that spent on creating AB3. We expect most typical teams to score near to 10. +
-1. _{ more test cases …​ }_ +Difficulty Level: 12 + +#### Effort required +Justification for the difficulty level: +1. **Moving parts** - While AB3 uses only one entity type, Persons, which was adapted to be our Student, we have +multiple entities, namely `Student`, `Task`, `TutorialGroup`, and `Grade`. This means that we have to implement +multiple classes, and multiple classes that interact with each other. +2. **Different purposes** - While _some_ of the code for AB3 commands could be reused, there were many cases where +the code had to be redesigned and rewritten in order to fit the purpose of our application. + 1. For example, the Task Edit Command had to be completely rewritten because we required the ability to +search through the current model's list of students using a list of strings, and then generating a CommandOutput +from that + 2. Other examples include the all the Grade Commands, which had to be written from scratch, as well as the +Tutorial Group filtering. + 3. The grade command uses a Map to store the grades of students, which is a new data structure that we had to +to add into the code, not previously used in AB3. +3. **New features** - We have many new features that were not present in AB3, such as the ability to grade students, +filter, as well as introducing tasks, tutorial groups and grades. + +### Challenges Faced + +1. **Learning JavaFX** - We had to learn JavaFX in order to implement the GUI. This was a challenge because +there was a steep learning curve, and we had to learn how to use FXML, as well as how to use the JavaFX API. +Our efforts include: + 1. **Creating two views** -- one for Tasks and another for Students, which the user can switch between by clicking +on the buttons on the left side of the screen. + 2. **Making the UI interactive**, such as the ability to double click on a task to view the students assigned to the +task. We had to handle the special case of a task having no students assigned to it by learning how to block users' +clicks. + 3. **Making the UI responsive** to resizing by learning how layout, padding, and margins work hand-in-hand to ensure +that UI components remain well aligned, properly spaced, and sufficiently large to accommodate text. +2. **Implementing complex features** - We had to implement many complex features, such as the ability to grade, +edit tasks, and filter by tutorial group. This was a challenge because we had to design the implementation from +scratch and think about the the relationships between the different classes. +3. **Learning Git** - We had to learn how to use Git in order to collaborate on the project. This was a challenge +as most of us had never used Git before, and we had to learn how to use it in order to collaborate on the project. diff --git a/docs/Gemfile b/docs/Gemfile index 999a7099d8d..71470c27332 100644 --- a/docs/Gemfile +++ b/docs/Gemfile @@ -7,3 +7,5 @@ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } gem 'jekyll' gem 'github-pages', group: :jekyll_plugins gem 'wdm', '~> 0.1.0' if Gem.win_platform? + +gem "webrick", "~> 1.7" diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index a32e7cafcd6..b19e219a980 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -1,101 +1,104 @@ GEM remote: https://rubygems.org/ specs: - activesupport (6.0.3.1) + activesupport (6.0.6) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) zeitwerk (~> 2.2, >= 2.2.2) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + addressable (2.8.1) + public_suffix (>= 2.0.2, < 6.0) coffee-script (2.4.1) coffee-script-source execjs coffee-script-source (1.11.1) colorator (1.1.0) - commonmarker (0.17.13) - ruby-enum (~> 0.5) - concurrent-ruby (1.1.6) - dnsruby (1.61.3) - addressable (~> 2.5) - em-websocket (0.5.1) + commonmarker (0.23.6) + concurrent-ruby (1.1.10) + dnsruby (1.61.9) + simpleidn (~> 0.1) + em-websocket (0.5.3) eventmachine (>= 0.12.9) - http_parser.rb (~> 0.6.0) - ethon (0.12.0) - ffi (>= 1.3.0) + http_parser.rb (~> 0) + ethon (0.16.0) + ffi (>= 1.15.0) eventmachine (1.2.7) eventmachine (1.2.7-x64-mingw32) - execjs (2.7.0) - faraday (1.0.1) - multipart-post (>= 1.2, < 3) - ffi (1.12.2) - ffi (1.12.2-x64-mingw32) + execjs (2.8.1) + faraday (2.6.0) + faraday-net_http (>= 2.0, < 3.1) + ruby2_keywords (>= 0.0.4) + faraday-net_http (3.0.1) + ffi (1.15.5) + ffi (1.15.5-x64-mingw32) forwardable-extended (2.6.0) gemoji (3.0.1) - github-pages (204) - github-pages-health-check (= 1.16.1) - jekyll (= 3.8.5) + github-pages (227) + github-pages-health-check (= 1.17.9) + jekyll (= 3.9.2) jekyll-avatar (= 0.7.0) jekyll-coffeescript (= 1.1.1) - jekyll-commonmark-ghpages (= 0.1.6) + jekyll-commonmark-ghpages (= 0.2.0) jekyll-default-layout (= 0.1.4) - jekyll-feed (= 0.13.0) + jekyll-feed (= 0.15.1) jekyll-gist (= 1.5.0) jekyll-github-metadata (= 2.13.0) - jekyll-mentions (= 1.5.1) + jekyll-include-cache (= 0.2.1) + jekyll-mentions (= 1.6.0) jekyll-optional-front-matter (= 0.3.2) jekyll-paginate (= 1.1.0) jekyll-readme-index (= 0.3.0) - jekyll-redirect-from (= 0.15.0) + jekyll-redirect-from (= 0.16.0) jekyll-relative-links (= 0.6.1) - jekyll-remote-theme (= 0.4.1) + jekyll-remote-theme (= 0.4.3) jekyll-sass-converter (= 1.5.2) - jekyll-seo-tag (= 2.6.1) + jekyll-seo-tag (= 2.8.0) jekyll-sitemap (= 1.4.0) jekyll-swiss (= 1.0.0) - jekyll-theme-architect (= 0.1.1) - jekyll-theme-cayman (= 0.1.1) - jekyll-theme-dinky (= 0.1.1) - jekyll-theme-hacker (= 0.1.1) - jekyll-theme-leap-day (= 0.1.1) - jekyll-theme-merlot (= 0.1.1) - jekyll-theme-midnight (= 0.1.1) - jekyll-theme-minimal (= 0.1.1) - jekyll-theme-modernist (= 0.1.1) - jekyll-theme-primer (= 0.5.4) - jekyll-theme-slate (= 0.1.1) - jekyll-theme-tactile (= 0.1.1) - jekyll-theme-time-machine (= 0.1.1) + jekyll-theme-architect (= 0.2.0) + jekyll-theme-cayman (= 0.2.0) + jekyll-theme-dinky (= 0.2.0) + jekyll-theme-hacker (= 0.2.0) + jekyll-theme-leap-day (= 0.2.0) + jekyll-theme-merlot (= 0.2.0) + jekyll-theme-midnight (= 0.2.0) + jekyll-theme-minimal (= 0.2.0) + jekyll-theme-modernist (= 0.2.0) + jekyll-theme-primer (= 0.6.0) + jekyll-theme-slate (= 0.2.0) + jekyll-theme-tactile (= 0.2.0) + jekyll-theme-time-machine (= 0.2.0) jekyll-titles-from-headings (= 0.5.3) - jemoji (= 0.11.1) - kramdown (= 1.17.0) + jemoji (= 0.12.0) + kramdown (= 2.3.2) + kramdown-parser-gfm (= 1.1.0) liquid (= 4.0.3) mercenary (~> 0.3) minima (= 2.5.1) - nokogiri (>= 1.10.4, < 2.0) - rouge (= 3.13.0) + nokogiri (>= 1.13.6, < 2.0) + rouge (= 3.26.0) terminal-table (~> 1.4) - github-pages-health-check (1.16.1) + github-pages-health-check (1.17.9) addressable (~> 2.3) dnsruby (~> 1.60) octokit (~> 4.0) - public_suffix (~> 3.0) + public_suffix (>= 3.0, < 5.0) typhoeus (~> 1.3) - html-pipeline (2.12.3) + html-pipeline (2.14.3) activesupport (>= 2) nokogiri (>= 1.4) - http_parser.rb (0.6.0) + http_parser.rb (0.8.0) i18n (0.9.5) concurrent-ruby (~> 1.0) - jekyll (3.8.5) + jekyll (3.9.2) addressable (~> 2.4) colorator (~> 1.0) em-websocket (~> 0.5) i18n (~> 0.7) jekyll-sass-converter (~> 1.0) jekyll-watch (~> 2.0) - kramdown (~> 1.14) + kramdown (>= 1.17, < 3) liquid (~> 4.0) mercenary (~> 0.3.3) pathutil (~> 0.9) @@ -106,23 +109,25 @@ GEM jekyll-coffeescript (1.1.1) coffee-script (~> 2.2) coffee-script-source (~> 1.11.1) - jekyll-commonmark (1.3.1) - commonmarker (~> 0.14) - jekyll (>= 3.7, < 5.0) - jekyll-commonmark-ghpages (0.1.6) - commonmarker (~> 0.17.6) - jekyll-commonmark (~> 1.2) + jekyll-commonmark (1.4.0) + commonmarker (~> 0.22) + jekyll-commonmark-ghpages (0.2.0) + commonmarker (~> 0.23.4) + jekyll (~> 3.9.0) + jekyll-commonmark (~> 1.4.0) rouge (>= 2.0, < 4.0) jekyll-default-layout (0.1.4) jekyll (~> 3.0) - jekyll-feed (0.13.0) + jekyll-feed (0.15.1) jekyll (>= 3.7, < 5.0) jekyll-gist (1.5.0) octokit (~> 4.2) jekyll-github-metadata (2.13.0) jekyll (>= 3.4, < 5.0) octokit (~> 4.0, != 4.4.0) - jekyll-mentions (1.5.1) + jekyll-include-cache (0.2.1) + jekyll (>= 3.7, < 5.0) + jekyll-mentions (1.6.0) html-pipeline (~> 2.3) jekyll (>= 3.7, < 5.0) jekyll-optional-front-matter (0.3.2) @@ -130,72 +135,76 @@ GEM jekyll-paginate (1.1.0) jekyll-readme-index (0.3.0) jekyll (>= 3.0, < 5.0) - jekyll-redirect-from (0.15.0) + jekyll-redirect-from (0.16.0) jekyll (>= 3.3, < 5.0) jekyll-relative-links (0.6.1) jekyll (>= 3.3, < 5.0) - jekyll-remote-theme (0.4.1) + jekyll-remote-theme (0.4.3) addressable (~> 2.0) jekyll (>= 3.5, < 5.0) - rubyzip (>= 1.3.0) + jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) + rubyzip (>= 1.3.0, < 3.0) jekyll-sass-converter (1.5.2) sass (~> 3.4) - jekyll-seo-tag (2.6.1) - jekyll (>= 3.3, < 5.0) + jekyll-seo-tag (2.8.0) + jekyll (>= 3.8, < 5.0) jekyll-sitemap (1.4.0) jekyll (>= 3.7, < 5.0) jekyll-swiss (1.0.0) - jekyll-theme-architect (0.1.1) - jekyll (~> 3.5) + jekyll-theme-architect (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-cayman (0.1.1) - jekyll (~> 3.5) + jekyll-theme-cayman (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-dinky (0.1.1) - jekyll (~> 3.5) + jekyll-theme-dinky (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-hacker (0.1.1) - jekyll (~> 3.5) + jekyll-theme-hacker (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-leap-day (0.1.1) - jekyll (~> 3.5) + jekyll-theme-leap-day (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-merlot (0.1.1) - jekyll (~> 3.5) + jekyll-theme-merlot (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-midnight (0.1.1) - jekyll (~> 3.5) + jekyll-theme-midnight (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-minimal (0.1.1) - jekyll (~> 3.5) + jekyll-theme-minimal (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-modernist (0.1.1) - jekyll (~> 3.5) + jekyll-theme-modernist (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-primer (0.5.4) + jekyll-theme-primer (0.6.0) jekyll (> 3.5, < 5.0) jekyll-github-metadata (~> 2.9) jekyll-seo-tag (~> 2.0) - jekyll-theme-slate (0.1.1) - jekyll (~> 3.5) + jekyll-theme-slate (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-tactile (0.1.1) - jekyll (~> 3.5) + jekyll-theme-tactile (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-time-machine (0.1.1) - jekyll (~> 3.5) + jekyll-theme-time-machine (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) jekyll-titles-from-headings (0.5.3) jekyll (>= 3.3, < 5.0) jekyll-watch (2.2.1) listen (~> 3.0) - jemoji (0.11.1) + jemoji (0.12.0) gemoji (~> 3.0) html-pipeline (~> 2.2) jekyll (>= 3.0, < 5.0) - kramdown (1.17.0) + kramdown (2.3.2) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) liquid (4.0.3) - listen (3.2.1) + listen (3.7.1) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) mercenary (0.3.6) @@ -204,36 +213,37 @@ GEM jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) - minitest (5.14.1) - multipart-post (2.1.1) - nokogiri (1.13.6) + minitest (5.16.3) + nokogiri (1.13.9) mini_portile2 (~> 2.8.0) racc (~> 1.4) - nokogiri (1.13.6-x64-mingw32) + nokogiri (1.13.9-x64-mingw32) racc (~> 1.4) - octokit (4.18.0) - faraday (>= 0.9) - sawyer (~> 0.8.0, >= 0.5.3) + octokit (4.25.1) + faraday (>= 1, < 3) + sawyer (~> 0.9) pathutil (0.16.2) forwardable-extended (~> 2.6) - public_suffix (3.1.1) + public_suffix (4.0.7) racc (1.6.0) - rb-fsevent (0.10.4) + rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - rouge (3.13.0) - ruby-enum (0.8.0) - i18n - rubyzip (2.3.0) + rexml (3.2.5) + rouge (3.26.0) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) safe_yaml (1.0.5) sass (3.7.4) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - sawyer (0.8.2) + sawyer (0.9.2) addressable (>= 2.3.5) - faraday (> 0.8, < 2.0) + faraday (>= 0.17.3, < 3) + simpleidn (0.2.1) + unf (~> 0.1.4) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) thread_safe (0.3.6) @@ -241,8 +251,13 @@ GEM ethon (>= 0.9.0) tzinfo (1.2.10) thread_safe (~> 0.1) - unicode-display_width (1.7.0) - zeitwerk (2.3.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.8.2) + unf_ext (0.0.8.2-x64-mingw32) + unicode-display_width (1.8.0) + webrick (1.7.0) + zeitwerk (2.6.4) PLATFORMS ruby @@ -251,6 +266,7 @@ PLATFORMS DEPENDENCIES github-pages jekyll + webrick (~> 1.7) BUNDLED WITH - 2.1.4 + 2.3.22 diff --git a/docs/UseCase.md b/docs/UseCase.md new file mode 100644 index 00000000000..37af2fb0ae9 --- /dev/null +++ b/docs/UseCase.md @@ -0,0 +1,54 @@ +Use Case + +System: Teaching Assistant Assistant (TAA) + +UC1 - Add student +Actor: User +MSS: + +1. User chooses to add a student record. +2. TAA displays the page for adding student. +3. User inputs the details of the student. +4. User confirms the details. +5. TAA add the record to the system. + +Extensions +3a. TAA checks for duplicates and incorrect formating. +3a1. TAA requests for correct data and format. +3a2. User inputs the correct data. +Steps 3a1-3a2 are repeated until the data entered are correct. +Use case resumes from step 4. + +UC2 - Edit student's record. +Actor: User +MSS: + +1. User searches for his/her name or ID. +2. TAA displays all the students records with a matching signature. +3. User chooses the correct student. +4. User edit the student record. +5. User confirms the update. +6. TAA saves the new student record. + +Extensions +2a. TAA cannot find any student record with a matching signature. +2b. TAA requests for re-enter. +2c. User re-enter the student name or ID +Use case resume from step 3. + +UC3 - Tag a student who has not finished his/her homework +Actor: User +MSS: + +1. User searches for his/her name or ID. +2. TAA displays all the students records with a matching signature. +3. User chooses the correct student. +4. User chooses the type of tag. +5. User confirms the tagging action. +6. TAA register the tag and update accordingly. + +Extensions +2a. TAA cannot find any student record with a matching signature. +2b. TAA requests for re-enter. +2c. User re-enter the student name or ID +Use case resume from step 3. diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 3716f3ca8a4..058a53513f4 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -3,190 +3,436 @@ 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. +## Welcome to the TAA User Guide! +![TAA](/images/taa_main.png) + +*Teaching Assistant Assistant (TAA)* is a **desktop app for Teaching Assistants (TA) to track student progress and tasks, +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, TAA can get your students and task management done +faster than traditional GUI apps. + +### Using this guide +If you are viewing this for the first time, you may want to read the [Getting Started](#getting-started) section first. +* Check out our [Quick Start](#quick-start) section for a quick overview of setting up the app for the first time. +* If you are a new user, you may want to check out the [Command Summary](#command-summary) section +for a quick overview of the commands. +* If you have any questions, do check out our [FAQ](#FAQ) section for answers to common quest.ons. +* If you are interested in helping to develop TAA, check out our [Developer Guide](DeveloperGuide.md). + +--- * Table of Contents {:toc} --------------------------------------------------------------------------------------------------------------------- +--- + +## Getting Started -## Quick start +If you are new to TAA, this section serves as an introduction to the app and its functionalities. + +### What is TAA? +TAA is a student developed, open source desktop application for **Teaching Assistants (TAs) to track students' progress +and tasks.** + +### Quick Start 1. Ensure you have Java `11` or above installed in your Computer. -1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases). +2. Download the latest TAA.jar from [here](https://github.com/AY2223S1-CS2103T-T13-1/tp/releases). + +3. Copy the file to the folder you want to use as the home folder for your TAA. + +4. Double-click the file to start the app. The GUI appear in your app should be similar as the one shown below:
+![TAA](/images/taa_main.png) + +5. Type the command in the command box and press Enter or click the Send button to execute. Some example commands you can try: + * `task add tn/Assignment 1 i/Due ASAP d/12/12/2022`: Creates a task called Assignment 1. + * `task edit 1 d/30/12/2022`: Edits the task Assignment 1 to change its deadline to 30/12/2022. + * `task delete 1`: Removes the task Assignment 1 from TAA. + +6. Refer to the Features below for details of each command. Alternatively, you may refer to the +[Command Summary](#Command Summary) section for a quick overview of the commands. + +--- + +## Command Summary + +This section shows a quick summary of the list of commands that are available in TAA. + +### Student Management -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +| Action | Format, Examples | +|-------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **[Add student](#add-students)** | `student add n/ p/ e/ g/(optional) t/(optional)` e.g. `student add n/James Ho p/98765432 e/a@gmail.com g/T03 t/yearTwo` | +| **[Remove student(s)](#remove-students)** | `student delete ` e.g. `student delete 1 3` | +| **[Edit student](#edit-students)** | `student edit n/(optional) p/(optional) e/(optional) g/(optional) t/(optional)` e.g. `student edit 1 g/T05` | +| **[List students](#list-students)** | `student list` | -1. Double-click the file to start the app. The GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
- ![Ui](images/Ui.png) +### Task Management -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: +| Action | Format, Examples | +|------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **[Add task](#add-new-task)** | `task add tn/ i/ d/ s/(optional)` e.g. `task add tn/Grade Mission 1 i/Due Tomorrow d/10/12/2022 s/James Ho` | +| **[Remove task(s)](#remove-task)** | `task delete ` e.g. `task delete 2 4` | +| **[Edit task](#edit-task)** | `task edit tn/(optional) i/(optional) d/(optional) s/(optional)` e.g. `task edit 1 d/11/12/2020` | +| **[List tasks](#list-tasks)** | `task list` | - * **`list`** : Lists all contacts. +### Tutorial Groups - * **`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. +| Action | Format, Examples | +|---------------------------------------------------------------------------|---------------------------------------------------------------------| +| **[Add tutorial group](#add-new-tutorial-group)** | `tutorial add g/` e.g. `tutorial add g/T01` | +| **[Remove tutorial group](#remove-tutorial-group)** | `tutorial delete ` e.g. `tutorial delete 2` | +| **[Enrol student](#enroll-a-student-into-a-group)** | `student enroll g/` e.g. `student enrol 1 g/T03` | +| **[Expel student](#expel-a-student-from-a-group)** | `student expel g/` e.g. `student expel 1 g/T03` | +| **[Filter students by group](#view-all-students-in-a-tutorial-group)** | `tutorial filter g/` e.g. `tutorial filter g/T03` | +| **[Reset filters](#reset-filters-and-show-all-students)** | `student unfilter` | - * **`delete`**`3` : Deletes the 3rd contact shown in the current list. +### Grades - * **`clear`** : Deletes all contacts. +| Action | Format, Examples | +|---------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **[Mark assignment as graded](#mark-assignment-as-graded-or-ungraded)** | `grade edit gr/T` e.g. `grade edit 1 1 gr/T` | +| **[Mark assignment as ungraded](#mark-assignment-as-graded-or-ungraded)** | `grade edit gr/F` e.g. `grade edit 2 1 gr/F` | +| **[View assignment grading status](#display-assignment-grade-status)** | `grade view ` e.g. `grade view 3 7` | - * **`exit`** : Exits the app. +### General -1. Refer to the [Features](#features) below for details of each command. +| Action | Format, Examples | +|---------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **[Display the user guide URL](#display-user-guide-url)** | `help me` | +| **[Exit the app](#exit-the-app)** | `bye bye` | --------------------------------------------------------------------------------------------------------------------- +--- ## Features +This section will explain in detail the functionality of each command in TAA. + +Before we start, here are some definitions that will be used in this section:
**:information_source: Notes about the command format:**
-* 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`. +* Words surrounded in angled brackets (`< >`), e.g. `` are the parameters to be supplied by the user.
+ e.g. in `tutorial add g/`, `` is a parameter which can be used as `tutorial add g/T03`. -* 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`. +* Optional parameters are indicated with `(optional)`.
+ e.g. `task add tn/ i/ d/ s/(optional)` + can be used as + * `task add tn/Assignment 6 i/Recursion d/31/12/2023` or + * `task add tn/Assignment 6 i/Recursion d/31/12/2021 s/Thomas Edison` or + * `task add tn/Assignment 6 i/Recursion d/31/12/2021 s/Thomas Edison s/George Washington` -* 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. +* Arguments with `(s)` after them can be used multiple times including zero times.
+ e.g. See the above example regarding `s/`. -* 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. +* Parameters can be specified in any order.
+ e.g. if the command specifies `group expel g/ s/`, both of the following commands are equivalent: + * `group expel g/T03 s/Billy Boy` + * `group expel s/Billy Boy g/T03` -* 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. +* 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/80000000 p/88888888`, only `p/88888888` will be taken. -* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
- e.g. if the command specifies `help 123`, it will be interpreted as `help`. +* Extraneous parameters for commands that do not take in parameters (such as `help me`, `bye bye`) will be ignored.
+ e.g. if you enter `help me pretty p/lease`, it will be interpreted as `help me`. +- For commands that take an index, you can parse multiple indexes at once to do the action for multiple items at once. + * You can remove multiple students at once by separating the indices with a space. e.g. `student delete 1 2 3`
-### Viewing help : `help` +### Student Management +#### Add students + +Adds a student with the given phone number, email, tutorial group, and tags to the list of students + +- Command `student add n/ p/ e/ g/(optional) t/(optional)` + +E.g. `student add n/James Ho p/98765432 e/a@gmail.com g/T03 t/yearTwo` + +
+ +**:information_source: Notes:**
+ +- `` should be alphanumeric and should not be blank +- `` should only contain numbers, and it should be at least 3 digits long +- `` should contain in the format: local-part@domain + - local-part should only contain letters, numbers, and the special characters `+`, `_`, `.`, and `-` + - local-part should not start or end with the four special characters mentioned above + - domain should be at least 2 characters long +- `` should follow the format Txx, where x is a numeric value, and it should not be blank +- `` should be alphanumeric with no white space +
+ +#### Remove students + +Removes the indexed students(s) from the list of students + +- Command `student delete ` + +E.g. `student delete 1 3` + +
-Shows a message explaning how to access the help page. +**:information_source: Notes:**
-![help message](images/helpMessage.png) +- Each `index` must be a positive integer which corresponds to an existing student +- You can delete multiple students at once by separating the indices with a space. e.g. `student delete 1 2 3` +
-Format: `help` +#### Edit students +Edits the student by its given index with at least 1 variable specified to change. -### Adding a person: `add` +- Command: `student edit n/(optional) p/(optional) e/(optional) g/(optional) t/(optional)` -Adds a person to the address book. +E.g. `student edit 1 g/T05` -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +
-
:bulb: **Tip:** -A person can have any number of tags (including 0) +**:information_source: Notes:**
+ +- `` must be a positive integer and must correspond to an existing student. +- `` should be alphanumeric and should not be blank +- `` should only contain numbers, and it should be at least 3 digits long +- `` should contain in the format: local-part@domain + - local-part should only contain letters, numbers, and the special characters `+`, `_`, `.`, and `-` + - local-part should not start or end with the four special characters mentioned above + - domain should be at least 2 characters long +- `` should follow the format Txx, where x is a numeric value, and it should not be blank +- `` should be alphanumeric with no white space +- For commands that take an index, you can parse multiple indexes at once to do the action for multiple items at once. + - You can edit multiple students at once by separating the indices with a space. e.g. `student edit 1 2 3 g/T03`
-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` +#### List students + +Shows a list of all students -### Listing all persons : `list` +- Command: `student list` -Shows a list of all persons in the address book. +![Student List](images/student list.png) -Format: `list` +### Task Management -### Editing a person : `edit` +#### Add new task +Adds a task to the list of tasks -Edits an existing person in the address book. +The task is initially assigned to no students if no student variable is entered +- Command: `task add tn/ i/ d/ s/(optional)` -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +E.g. `task add tn/Grade Mission 1 i/Due Tomorrow d/10/12/2022 s/James Ho` -* 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. +
-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. +**:information_source: Notes:**
-### Locating persons by name: `find` +- `taskName` should only contain alphanumeric characters and spaces, and it should not be blank +- `taskDescription` should not be blank +- `taskDeadline` should be in the format of DD/MM/YYYY with its days and months within range +- `student(s)`, referenced by their name, should exist +
-Finds persons whose names contain any of the given keywords. +#### Remove task -Format: `find KEYWORD [MORE_KEYWORDS]` +Removes the indexed task(s) from the list of tasks +- Command: `task delete ` -* 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` +E.g. `task delete 2 4` -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` +**:information_source: Notes:**
-Deletes the specified person from the address book. +- Each `index` must be a positive integer which corresponds to an existing task +- You can delete multiple tasks at once by separating the indices with a space. e.g. `task delete 1 2 3` +
-Format: `delete INDEX` +#### Edit task -* 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, …​ +Edits the task by its given index with at least 1 variable specified to change. + +- Command: `task edit tn/(optional) i/(optional) d/(optional) s/(optional)` + +E.g. `task edit 1 d/11/12/2020` + +
+ +**:information_source: Notes:**
+ +- `index` must be a positive integer and valid +- `taskName` should only contain alphanumeric characters and spaces, and it should not be blank +- `taskDescription` should not be blank +- `taskDeadline` should be in the format of DD/MM/YYYY with its days and months within range +- `student(s)`, referenced by their name, should exist +
-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. +#### List tasks -### Clearing all entries : `clear` +Shows a list of tasks -Clears all entries from the address book. +- Command: `task list` -Format: `clear` +![Task List](images/task list.png) -### Exiting the program : `exit` +### Tutorial Groups -Exits the program. +#### Add new tutorial group -Format: `exit` +Adds the tutorial group with the name `tutorialGroup` -### Saving the data +- Command: `tutorial add g/` -AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +E.g. `tutorial add g/T01` -### Editing the data file +
-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. +**:information_source: Notes:**
-
: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. +- `tutorialGroup` should follow the format Txx, where x is a numeric value, and it should not be blank
-### Archiving data files `[coming in v2.0]` +#### List tutorial groups -_Details coming soon ..._ +Shows a list of tutorial groups --------------------------------------------------------------------------------------------------------------------- +- Command: `tutorial list` -## FAQ +![Tutorial List](images/tutorial list.png) + +#### Remove tutorial group + +Removes the tutorial group at the specified `index` + +- Command: `tutorial delete ` + +E.g. `tutorial delete 1` + +
+ +**:information_source: Notes:**
+ +- `tutorialGroup` should follow the format Txx, where x is a numeric value, and it should not be blank +
+ +
+ +**:bulb: Tips:**
+ +- Before deleting a tutorial group, you may want to enter the `tutorial list` command first to see which index corresponds to the tutorial group meant to be deleted. +
+ +#### Enroll a student into a group + +Enrolls the student at index i to the group named `groupName` + +- Command: `student enroll g/` + +E.g. `student enrol 1 g/T03` + +
+ +**:information_source: Notes:**
+ +- `tutorialGroup` must follow the format Txx, where x is a numeric value, and it should not be blank +
+ +#### Expel a student from a group + +Removes the student at index i from the group `groupName`. + +- Command: `student expel g/` + +E.g. `student expel 1 g/T03` + +
+ +**:information_source: Notes:**
+ +- `tutorialGroup` must follow the format Txx, where x is a numeric value, and it should not be blank +
+ +#### View all students in a tutorial group + +Displays only students from the group `groupName` in the GUI. + +- Command: `tutorial filter g/` + +E.g. tutorial filter g/T01 + +![Tutorial Filter](images/tutorial%20filter.png) + +
+ +**:information_source: Notes:**
+ +- `tutorialGroup` must follow the format Txx, where x is a numeric value, and it should not be blank +- the specified `tutorialGroup` must exist +
+ +#### Reset filters and show all students -**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. +Undoes the `tutorial filter` command and displays all students in the GUI. + +- Command: `student unfilter` + +### Grades + +#### Mark assignment as graded or ungraded + +Marks the specified student's assignment as graded (`T`) or ungraded (`F`) + +- Command: `grade edit gr/` + +E.g. `grade edit 1 1 gr/T` + +
+ +**:information_source: Notes:**
+ +- Both `studentIndex` and `taskIndex` must be positive integers and valid indices +
+ +#### Display assignment grade status + +Shows the grade status of the specified student's assignment + +- Command: `grade view ` + +E.g. `grade view 3 7` + +
+ +**:information_source: Notes:**
+ +- Both `studentIndex` and `taskIndex` must be positive integers and valid indices +
+ +### General +#### Display user guide URL + +Shows a popup with the user guide URL in it + +- Command: `help me` + +#### Exit the app + +Exit and close the app + +- Command: `bye bye` + +--- + +## FAQ --------------------------------------------------------------------------------------------------------------------- +#### What if I forget the command format? +- You can click on the help button to view the commands. -## Command summary +#### Why can't I add a student? +- Check the format of your add command. You can refer to the pop-up message or the help page for the command format. +- Check if the name of the new student belongs to another student. -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` +#### Why can't I enroll a student to a tutorial group? +- Check whether the specified student and tutorial group exists. +- Check if you have entered the correct student name. diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..c6ee8288212 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "TAA" theme: minima header_pages: @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "AY2223S1-CS2103T-T13-1/tp" github_icon: "images/github-icon.png" plugins: diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss index 0d3f6e80ced..2811d9b59df 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: "TAA"; font-size: 32px; } } diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index ef81d18c337..b17fd6f0f2e 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -7,13 +7,13 @@ Participant ":Logic" as logic LOGIC_COLOR Participant ":Model" as model MODEL_COLOR Participant ":Storage" as storage STORAGE_COLOR -user -[USER_COLOR]> ui : "delete 1" +user -[USER_COLOR]> ui : "student delete 1" activate ui UI_COLOR -ui -[UI_COLOR]> logic : execute("delete 1") +ui -[UI_COLOR]> logic : execute("student delete 1") activate logic LOGIC_COLOR -logic -[LOGIC_COLOR]> model : deletePerson(p) +logic -[LOGIC_COLOR]> model : deleteStudent(p) activate model MODEL_COLOR model -[MODEL_COLOR]-> logic diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml index 5731f9cbaa1..67a47e6befe 100644 --- a/docs/diagrams/BetterModelClassDiagram.puml +++ b/docs/diagrams/BetterModelClassDiagram.puml @@ -4,18 +4,29 @@ skinparam arrowThickness 1.1 skinparam arrowColor MODEL_COLOR skinparam classBackgroundColor MODEL_COLOR -AddressBook *-right-> "1" UniquePersonList -AddressBook *-right-> "1" UniqueTagList -UniqueTagList -[hidden]down- UniquePersonList -UniqueTagList -[hidden]down- UniquePersonList +TaskManager *-right-> "1" UniqueTutorialGroupList +TaskManager *-right-> "1" UniqueStudentList +TaskManager *-right-> "1" UniqueTagList +TaskManager *-right-> "1" UniqueTaskList + +UniqueTagList -[hidden]down- UniqueStudentList +UniqueTagList -[hidden]down- UniqueStudentList +UniqueTagList -[hidden]down- UniqueTaskList UniqueTagList *-right-> "*" Tag -UniquePersonList -right-> Person +UniqueStudentList -right-> Student +UniqueTaskList -right-> Task + +Student -up-> "*" Tag + +Student *--> Name +Student *--> Phone +Student *--> Email +Student *--> TutorialGroup +UniqueTutorialGroupList *--> TutorialGroup -Person -up-> "*" Tag +Task *--> TaskName +Task *--> TaskDescription +Task *--> TaskDeadline -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address @enduml diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 1dc2311b245..ed3ea69261e 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -3,9 +3,9 @@ box Logic LOGIC_COLOR_T1 participant ":LogicManager" as LogicManager LOGIC_COLOR -participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR -participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR -participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR +participant ":TaaParser" as TaaParser LOGIC_COLOR +participant ":StudentDeleteCommandParser" as StudentDeleteCommandParser LOGIC_COLOR +participant "d:StudentDeleteCommand" as StudentDeleteCommand LOGIC_COLOR participant ":CommandResult" as CommandResult LOGIC_COLOR end box @@ -13,56 +13,56 @@ box Model MODEL_COLOR_T1 participant ":Model" as Model MODEL_COLOR end box -[-> LogicManager : execute("delete 1") +[-> LogicManager : execute("student delete 1") activate LogicManager -LogicManager -> AddressBookParser : parseCommand("delete 1") -activate AddressBookParser +LogicManager -> TaaParser : parseCommand("student delete 1") +activate TaaParser -create DeleteCommandParser -AddressBookParser -> DeleteCommandParser -activate DeleteCommandParser +create StudentDeleteCommandParser +TaaParser -> StudentDeleteCommandParser +activate StudentDeleteCommandParser -DeleteCommandParser --> AddressBookParser -deactivate DeleteCommandParser +StudentDeleteCommandParser --> TaaParser +deactivate StudentDeleteCommandParser -AddressBookParser -> DeleteCommandParser : parse("1") -activate DeleteCommandParser +TaaParser -> StudentDeleteCommandParser : parse("1") +activate StudentDeleteCommandParser -create DeleteCommand -DeleteCommandParser -> DeleteCommand -activate DeleteCommand +create StudentDeleteCommand +StudentDeleteCommandParser -> StudentDeleteCommand +activate StudentDeleteCommand -DeleteCommand --> DeleteCommandParser : d -deactivate DeleteCommand +StudentDeleteCommand --> StudentDeleteCommandParser : d +deactivate StudentDeleteCommand -DeleteCommandParser --> AddressBookParser : d -deactivate DeleteCommandParser +StudentDeleteCommandParser --> TaaParser : d +deactivate StudentDeleteCommandParser 'Hidden arrow to position the destroy marker below the end of the activation bar. -DeleteCommandParser -[hidden]-> AddressBookParser -destroy DeleteCommandParser +StudentDeleteCommandParser -[hidden]-> TaaParser +destroy StudentDeleteCommandParser -AddressBookParser --> LogicManager : d -deactivate AddressBookParser +TaaParser --> LogicManager : d +deactivate TaaParser -LogicManager -> DeleteCommand : execute() -activate DeleteCommand +LogicManager -> StudentDeleteCommand : execute() +activate StudentDeleteCommand -DeleteCommand -> Model : deletePerson(1) +StudentDeleteCommand -> Model : deleteStudent(1) activate Model -Model --> DeleteCommand +Model --> StudentDeleteCommand deactivate Model create CommandResult -DeleteCommand -> CommandResult +StudentDeleteCommand -> CommandResult activate CommandResult -CommandResult --> DeleteCommand +CommandResult --> StudentDeleteCommand deactivate CommandResult -DeleteCommand --> LogicManager : result -deactivate DeleteCommand +StudentDeleteCommand --> LogicManager : result +deactivate StudentDeleteCommand [<--LogicManager deactivate LogicManager diff --git a/docs/diagrams/GradeEditActivityDiagram.puml b/docs/diagrams/GradeEditActivityDiagram.puml new file mode 100644 index 00000000000..4b6c5aeb450 --- /dev/null +++ b/docs/diagrams/GradeEditActivityDiagram.puml @@ -0,0 +1,17 @@ +@startuml +start +:User enters a GradeEditCommand; +:GradeEditCommandParser parses for valid format; +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +if () then ([grade edit command is valid]) + :Create new grade edit command which is executed by LogicManager; + :Model changes grade of the specified student-task pair; + :UI displays new grade in the Command Output box; + :UI updates the grade in the Task List Card; +else ([else]) + :Show error message; +endif +stop +@enduml diff --git a/docs/diagrams/GradeEditSequenceDiagram.puml b/docs/diagrams/GradeEditSequenceDiagram.puml new file mode 100644 index 00000000000..8417e64c986 --- /dev/null +++ b/docs/diagrams/GradeEditSequenceDiagram.puml @@ -0,0 +1,69 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":TAAParser" as TAAParser LOGIC_COLOR +participant ":GradeEditCommandParser" as GradeEditCommandParser LOGIC_COLOR +participant "g:GradeEditCommand" as GradeEditCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("grade edit 1 2 gr/T") +activate LogicManager + +LogicManager -> TAAParser : parse("grade edit 1 2 gr/T") +activate TAAParser + +create GradeEditCommandParser +TAAParser -> GradeEditCommandParser +activate GradeEditCommandParser + +GradeEditCommandParser --> TAAParser +deactivate GradeEditCommandParser + +TAAParser -> GradeEditCommandParser : parse("1 2 gr/T") +activate GradeEditCommandParser + +create GradeEditCommand +GradeEditCommandParser -> GradeEditCommand +activate GradeEditCommand + +GradeEditCommand --> GradeEditCommandParser : g +deactivate GradeEditCommand + +GradeEditCommandParser --> TAAParser : g +deactivate GradeEditCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +GradeEditCommandParser -[hidden]-> TAAParser +destroy GradeEditCommandParser + +TAAParser --> LogicManager : g +deactivate TAAParser + +LogicManager -> GradeEditCommand : execute() +activate GradeEditCommand + +GradeEditCommand -> Model : addGrade(1, 2, GRADED) +activate Model + +Model --> GradeEditCommand +deactivate Model + +create CommandResult +GradeEditCommand -> CommandResult +activate CommandResult + +CommandResult --> GradeEditCommand +deactivate CommandResult + +GradeEditCommand --> LogicManager : result +deactivate GradeEditCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/GradeKeyClass.puml b/docs/diagrams/GradeKeyClass.puml new file mode 100644 index 00000000000..ec4a40390d6 --- /dev/null +++ b/docs/diagrams/GradeKeyClass.puml @@ -0,0 +1,14 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +class GradeKey { +} + +GradeKey --> Student +GradeKey --> Task + + +@enduml diff --git a/docs/diagrams/GradeMapClassDiagram.puml b/docs/diagrams/GradeMapClassDiagram.puml new file mode 100644 index 00000000000..e27998634ce --- /dev/null +++ b/docs/diagrams/GradeMapClassDiagram.puml @@ -0,0 +1,17 @@ +@startuml +!include style.puml +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR +skinparam classBackgroundColor MODEL_COLOR + +enum Grade { + GRADED + UNGRADED +} + +show Grade members + +GradeMap "[GradeKey]" --> "1" Grade + + +@enduml diff --git a/docs/diagrams/GradeViewActivityDiagram.puml b/docs/diagrams/GradeViewActivityDiagram.puml new file mode 100644 index 00000000000..bf85d66d429 --- /dev/null +++ b/docs/diagrams/GradeViewActivityDiagram.puml @@ -0,0 +1,15 @@ +@startuml +start +:User enters a GradeViewCommand; +:GradeViewCommandParser parses for valid format; +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +if () then ([grade view command is valid]) + :Create new grade view command which is executed by LogicManager; + :UI updates the Command Output box with the requested grade; +else ([else]) + :Show error message; +endif +stop +@enduml diff --git a/docs/diagrams/GradeViewSequenceDiagram.puml b/docs/diagrams/GradeViewSequenceDiagram.puml new file mode 100644 index 00000000000..614aead430e --- /dev/null +++ b/docs/diagrams/GradeViewSequenceDiagram.puml @@ -0,0 +1,69 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":TAAParser" as TAAParser LOGIC_COLOR +participant ":GradeViewCommandParser" as GradeViewCommandParser LOGIC_COLOR +participant "g:GradeViewCommand" as GradeViewCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("grade view 1 2") +activate LogicManager + +LogicManager -> TAAParser : parse("grade view 1 2") +activate TAAParser + +create GradeViewCommandParser +TAAParser -> GradeViewCommandParser +activate GradeViewCommandParser + +GradeViewCommandParser --> TAAParser +deactivate GradeViewCommandParser + +TAAParser -> GradeViewCommandParser : parse("1 2") +activate GradeViewCommandParser + +create GradeViewCommand +GradeViewCommandParser -> GradeViewCommand +activate GradeViewCommand + +GradeViewCommand --> GradeViewCommandParser : g +deactivate GradeViewCommand + +GradeViewCommandParser --> TAAParser : g +deactivate GradeViewCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +GradeViewCommandParser -[hidden]-> TAAParser +destroy GradeViewCommandParser + +TAAParser --> LogicManager : g +deactivate TAAParser + +LogicManager -> GradeViewCommand : execute() +activate GradeViewCommand + +GradeViewCommand -> Model : getGrade(1, 2) +activate Model + +Model --> GradeViewCommand +deactivate Model + +create CommandResult +GradeViewCommand -> CommandResult +activate CommandResult + +CommandResult --> GradeViewCommand +deactivate CommandResult + +GradeViewCommand --> LogicManager : result +deactivate GradeViewCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/LogicClassDiagram.puml b/docs/diagrams/LogicClassDiagram.puml index d4193173e18..d65f9dd01f5 100644 --- a/docs/diagrams/LogicClassDiagram.puml +++ b/docs/diagrams/LogicClassDiagram.puml @@ -6,7 +6,9 @@ skinparam classBackgroundColor LOGIC_COLOR package Logic { -Class AddressBookParser +Class TaaParser +Class ABCCommandParser +Class ABCCommand Class XYZCommand Class CommandResult Class "{abstract}\nCommand" as Command @@ -27,10 +29,13 @@ Class HiddenOutside #FFFFFF HiddenOutside ..> Logic LogicManager .right.|> Logic -LogicManager -right->"1" AddressBookParser -AddressBookParser ..> XYZCommand : creates > +LogicManager -right->"1" TaaParser +TaaParser .right.> XYZCommand : directly\ncreates > +TaaParser ..> ABCCommandParser : creates > +ABCCommandParser ..> ABCCommand : creates > XYZCommand -up-|> Command +ABCCommand -up-|> Command LogicManager .left.> Command : executes > LogicManager --> Model @@ -38,8 +43,8 @@ LogicManager --> Storage Storage --[hidden] Model Command .[hidden]up.> Storage Command .right.> Model -note right of XYZCommand: XYZCommand = AddCommand, \nFindCommand, etc - +note bottom of XYZCommand: XYZCommand represents any\ncommand with no arguments, e.g.\nTaskListCommand and \nHelpCommand. +note bottom of ABCCommand: ABCCommand represents any\ncommand with arguments, e.g.\nTaskDeleteCommand and\nTutorialGroupAddCommand. Logic ..> CommandResult LogicManager .down.> CommandResult Command .up.> CommandResult : produces > diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 4439108973a..4fb12cf00ce 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -5,46 +5,61 @@ skinparam arrowColor MODEL_COLOR skinparam classBackgroundColor MODEL_COLOR Package Model <>{ -Class "<>\nReadOnlyAddressBook" as ReadOnlyAddressBook +Class "<>\nReadOnlyTaskManager" as ReadOnlyTaskManager Class "<>\nReadOnlyUserPrefs" as ReadOnlyUserPrefs Class "<>\nModel" as Model -Class AddressBook +Class TaskManager Class ModelManager Class UserPrefs -Class UniquePersonList -Class Person -Class Address +Class UniqueTaskList +Class UniqueStudentList +Class UniqueTutorialGroupList +Class Student +Class Task Class Email Class Name Class Phone +Class TutorialGroup Class Tag - +Class TaskName +Class TaskDeadline +Class TaskDescription +Class GradeMap } Class HiddenOutside #FFFFFF HiddenOutside ..> Model -AddressBook .up.|> ReadOnlyAddressBook +TaskManager .up.|> ReadOnlyTaskManager ModelManager .up.|> Model Model .right.> ReadOnlyUserPrefs -Model .left.> ReadOnlyAddressBook -ModelManager -left-> "1" AddressBook +Model .left.> ReadOnlyTaskManager +ModelManager -left-> "1" TaskManager ModelManager -right-> "1" UserPrefs UserPrefs .up.|> ReadOnlyUserPrefs -AddressBook *--> "1" UniquePersonList -UniquePersonList --> "~* all" Person -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address -Person *--> "*" Tag +TaskManager *--> "1" UniqueStudentList +TaskManager *--> "1" UniqueTaskList +TaskManager *--> "1" UniqueTutorialGroupList +TaskManager *--> "1" GradeMap +UniqueStudentList --> "~* all" Student +UniqueTaskList --> "~* all" Task +UniqueTutorialGroupList --> "~* all" TutorialGroup +Student *--> Name +Student *--> Phone +Student *--> Email +Student *--> "*" Tag +Student *--> TutorialGroup + +Task *--> TaskName +Task *--> TaskDescription +Task *--> TaskDeadline Name -[hidden]right-> Phone -Phone -[hidden]right-> Address -Address -[hidden]right-> Email +TutorialGroup -[hidden]right-> TutorialGroup -ModelManager -->"~* filtered" Person +ModelManager -->"~* filtered" Student +ModelManager -->"~* filtered" Task @enduml diff --git a/docs/diagrams/ParserClasses.puml b/docs/diagrams/ParserClasses.puml index 0c7424de6e0..0dd0afae003 100644 --- a/docs/diagrams/ParserClasses.puml +++ b/docs/diagrams/ParserClasses.puml @@ -5,12 +5,12 @@ skinparam arrowColor LOGIC_COLOR_T4 skinparam classBackgroundColor LOGIC_COLOR Class "{abstract}\nCommand" as Command -Class XYZCommand +Class ABCCommand package "Parser classes"{ Class "<>\nParser" as Parser -Class AddressBookParser -Class XYZCommandParser +Class TaaParser +Class ABCCommandParser Class CliSyntax Class ParserUtil Class ArgumentMultimap @@ -19,20 +19,20 @@ Class Prefix } Class HiddenOutside #FFFFFF -HiddenOutside ..> AddressBookParser +HiddenOutside ..> TaaParser -AddressBookParser .down.> XYZCommandParser: creates > +TaaParser .down.> ABCCommandParser: creates > -XYZCommandParser ..> XYZCommand : creates > -AddressBookParser ..> Command : returns > -XYZCommandParser .up.|> Parser -XYZCommandParser ..> ArgumentMultimap -XYZCommandParser ..> ArgumentTokenizer +ABCCommandParser ..> ABCCommand : creates > +TaaParser ..> Command : returns > +ABCCommandParser .up.|> Parser +ABCCommandParser ..> ArgumentMultimap +ABCCommandParser ..> ArgumentTokenizer ArgumentTokenizer .left.> ArgumentMultimap -XYZCommandParser ..> CliSyntax +ABCCommandParser ..> CliSyntax CliSyntax ..> Prefix -XYZCommandParser ..> ParserUtil +ABCCommandParser ..> ParserUtil ParserUtil .down.> Prefix ArgumentTokenizer .down.> Prefix -XYZCommand -up-|> Command +ABCCommand -up-|> Command @enduml diff --git a/docs/diagrams/ParserSequenceDiagram.puml b/docs/diagrams/ParserSequenceDiagram.puml new file mode 100644 index 00000000000..9cfd114add1 --- /dev/null +++ b/docs/diagrams/ParserSequenceDiagram.puml @@ -0,0 +1,68 @@ +@startuml +!include style.puml + +box Parser LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as TaaParser LOGIC_COLOR +participant ":TutorialGroupAddCommandParser" as TutorialGroupAddCommandParser LOGIC_COLOR +participant ":TutorialGroupAddCommand" as TutorialGroupAddCommand LOGIC_COLOR + +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + + + +LogicManager -> TaaParser : parseCommand("group add g/T02") +activate TaaParser + +create TutorialGroupAddCommandParser +TaaParser -> TutorialGroupAddCommandParser +activate TutorialGroupAddCommandParser + +TutorialGroupAddCommandParser --> TaaParser +deactivate TutorialGroupAddCommandParser + +TaaParser -> TutorialGroupAddCommandParser : parse("T01") +activate TutorialGroupAddCommandParser + +create TutorialGroupAddCommand +TutorialGroupAddCommandParser -> TutorialGroupAddCommand +activate TutorialGroupAddCommand + +TutorialGroupAddCommand --> TutorialGroupAddCommandParser +deactivate TutorialGroupAddCommand + +TutorialGroupAddCommandParser --> TaaParser : tutorialGroupAddCommand +deactivate TutorialGroupAddCommandParser + +TutorialGroupAddCommandParser -[hidden]-> TaaParser +destroy TutorialGroupAddCommandParser + +TaaParser --> LogicManager : tutorialGroupAddCommand +deactivate TaaParser + +LogicManager -> TutorialGroupAddCommand : execute() +activate TutorialGroupAddCommand + +TutorialGroupAddCommand -> Model : AddTutorial("T01") +activate Model + +Model --> TutorialGroupAddCommand +deactivate Model + +create CommandResult +TutorialGroupAddCommand -> CommandResult +activate CommandResult + +CommandResult --> TutorialGroupAddCommand +deactivate CommandResult + +TutorialGroupAddCommand --> LogicManager : commandResult +deactivate TutorialGroupAddCommand + + +@enduml diff --git a/docs/diagrams/SortTask.puml b/docs/diagrams/SortTask.puml new file mode 100644 index 00000000000..812b91bbdb4 --- /dev/null +++ b/docs/diagrams/SortTask.puml @@ -0,0 +1,18 @@ +@startuml +!include style.puml + +box Model MODEL_COLOR_T1 +participant "tasks:UniqueTaskList" as UniqueTaskList MODEL_COLOR_T2 +participant "internalList:ObservableList" as ObservableList MODEL_COLOR_T2 +end box + +[-> UniqueTaskList : add(task) +activate UniqueTaskList + +UniqueTaskList -> UniqueTaskList : sortByDeadline() + + +UniqueTaskList -> ObservableList : task.sort(comparator) +activate ObservableList + +@enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index 760305e0e58..65e2a71e5f6 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -18,8 +18,10 @@ package "AddressBook Storage" #F4F6F6{ Class "<>\nAddressBookStorage" as AddressBookStorage Class JsonAddressBookStorage Class JsonSerializableAddressBook -Class JsonAdaptedPerson +Class JsonAdaptedStudent Class JsonAdaptedTag +Class JsonAdaptedTask +Class JsonAdaptedTutorialGroup } } @@ -37,7 +39,9 @@ Storage -right-|> AddressBookStorage JsonUserPrefsStorage .up.|> UserPrefsStorage JsonAddressBookStorage .up.|> AddressBookStorage JsonAddressBookStorage ..> JsonSerializableAddressBook -JsonSerializableAddressBook --> "*" JsonAdaptedPerson -JsonAdaptedPerson --> "*" JsonAdaptedTag +JsonSerializableAddressBook --> "*" JsonAdaptedStudent +JsonSerializableAddressBook --> "*" JsonAdaptedTask +JsonSerializableAddressBook --> "*" JsonAdaptedTutorialGroup +JsonAdaptedStudent --> "*" JsonAdaptedTag @enduml diff --git a/docs/diagrams/StudentEnrollActivityDiagram.puml b/docs/diagrams/StudentEnrollActivityDiagram.puml new file mode 100644 index 00000000000..198a3fd3fc7 --- /dev/null +++ b/docs/diagrams/StudentEnrollActivityDiagram.puml @@ -0,0 +1,28 @@ +@startuml +start +:User enters a StudentEnrollCommand; +:StudentEnrollCommandParser parses for valid format; +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +if () then ([student enroll command is valid]) + :Get the target student from the model; + if () then ([student already enrolled in a tutorial]) + :Show error message; + else () + :Search the model for the tutorial group names; + if () then ([tutorial group name exists]) + :Enroll the student to the tutorial group; + :Update the changed to the model; + :UI displays enroll message in the Command Output box; + else ([else]) + :Show error message; + endif + endif + + +else ([else]) + :Show error message; +endif +stop +@enduml diff --git a/docs/diagrams/TaskClassDiagram.puml b/docs/diagrams/TaskClassDiagram.puml new file mode 100644 index 00000000000..acc91bd4ddb --- /dev/null +++ b/docs/diagrams/TaskClassDiagram.puml @@ -0,0 +1,48 @@ +@startuml +'https://plantuml.com/class-diagram + +class Task { ++ TaskName getTaskName() ++ TaskDescription getTaskDescription() ++ TaskDeadline getTaskDeadline() +} + +class TaskName { ++ {static} String MESSAGE_CONSTRAINTS ++ {static} String VALIDATION_REGEX ++ String taskName + ++ {static} boolean isValidName(String test) +} + +class TaskDescription { ++ {static} String MESSAGE_CONSTRAINTS ++ {static} String VALIDATION_REGEX ++ String description + ++ {static} boolean isValidDescription(String test) +} + +class TaskDeadline { ++ {static} String MESSAGE_CONSTRAINTS ++ {static} String MESSAGE_INVALID_DATE ++ {static} String VALIDATION_REGEX +- {static} String DATE_FORMAT ++ Date deadline + ++ {static} boolean isInDeadlineFormat(String test) ++ {static} boolean isValidDate(String test) +} + +class Student { + +} + + +Task *--> "1" TaskName +Task *--> "1" TaskDescription +Task *--> "1" TaskDeadline +Task --> "*" Student + +@enduml + diff --git a/docs/diagrams/TaskEditActivityDiagram.puml b/docs/diagrams/TaskEditActivityDiagram.puml new file mode 100644 index 00000000000..c31fa913f37 --- /dev/null +++ b/docs/diagrams/TaskEditActivityDiagram.puml @@ -0,0 +1,18 @@ +@startuml +start +:User enters a TaskEditCommand; +:TaskEditCommandParser parses for valid format; +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +if () then ([task edit command is valid]) + :Create new task edit command which is executed by LogicManager; + :Create a list of string of student names to parse into the command; + :Search the model for the student names and return a final list of changes to be made; + :UI displays edited task in the Command Output box; + :UI updates the edited task in the Task List Card; +else ([else]) + :Show error message; +endif +stop +@enduml diff --git a/docs/diagrams/TaskEditSequenceDiagram.puml b/docs/diagrams/TaskEditSequenceDiagram.puml new file mode 100644 index 00000000000..aa0610b464a --- /dev/null +++ b/docs/diagrams/TaskEditSequenceDiagram.puml @@ -0,0 +1,75 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":TAAParser" as TAAParser LOGIC_COLOR +participant ":TaskEditCommandParser" as TaskEditCommandParser LOGIC_COLOR +participant "t:TaskEditCommand" as TaskEditCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("task edit 1 tn/hi") +activate LogicManager + +LogicManager -> TAAParser : parse("task edit 1 tn/hi") +activate TAAParser + +create TaskEditCommandParser +TAAParser -> TaskEditCommandParser +activate TaskEditCommandParser + +TaskEditCommandParser --> TAAParser +deactivate TaskEditCommandParser + +TAAParser -> TaskEditCommandParser : parse("1 tn/hi") +activate TaskEditCommandParser + +create TaskEditCommand +TaskEditCommandParser -> TaskEditCommand +activate TaskEditCommand + +TaskEditCommand --> TaskEditCommandParser : t +deactivate TaskEditCommand + +TaskEditCommandParser --> TAAParser : t +deactivate TaskEditCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +TaskEditCommandParser -[hidden]-> TAAParser +destroy TaskEditCommandParser + +TAAParser --> LogicManager : t +deactivate TAAParser + +LogicManager -> TaskEditCommand : execute() +activate TaskEditCommand + +TaskEditCommand -> Model : editTask(1, "hi") +activate Model + +Model --> TaskEditCommand +deactivate Model + +TaskEditCommand -> Model : updateGrade(1) +activate Model + +Model --> TaskEditCommand +deactivate Model + +create CommandResult +TaskEditCommand -> CommandResult +activate CommandResult + +CommandResult --> TaskEditCommand +deactivate CommandResult + +TaskEditCommand --> LogicManager : result +deactivate TaskEditCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/TaskExpandActivityDiagram.puml b/docs/diagrams/TaskExpandActivityDiagram.puml new file mode 100644 index 00000000000..fe5a5cd2a5c --- /dev/null +++ b/docs/diagrams/TaskExpandActivityDiagram.puml @@ -0,0 +1,22 @@ +@startuml +'https://plantuml.com/activity-diagram-beta + +start +if () then ([else]) +else ([task button is clicked]) + :Show available tasks; + if () then ([else]) + else ([task card is clicked]) + if () then ([else]) + else ([task.getStudents().size() != 0]) + if () then ([!isExpanded]) + : Expand card; + else ([isExpanded]) + : Collapse card; +endif +endif +endif +endif +stop + +@enduml diff --git a/docs/diagrams/TutorialAddActivityDiagram.puml b/docs/diagrams/TutorialAddActivityDiagram.puml new file mode 100644 index 00000000000..bb8148d87b2 --- /dev/null +++ b/docs/diagrams/TutorialAddActivityDiagram.puml @@ -0,0 +1,21 @@ +@startuml +start +:User enters a TutorialAddCommand; +:TutorialAddCommandParser parses for valid format; +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +if () then ([tutorial add command is valid]) + :Create new tutorial add command which is executed by LogicManager; + :Search the model for the tutorial group names; + if () then ([tutorial group name already exists]) + :Show error message; + else ([else]) + :Create a tutorial group with the given name; + :UI displays added tutorial group in the Command Output box; + endif +else ([else]) + :Show error message; +endif +stop +@enduml diff --git a/docs/diagrams/TutorialFilterSequenceDiagram.puml b/docs/diagrams/TutorialFilterSequenceDiagram.puml new file mode 100644 index 00000000000..5408e3b37a7 --- /dev/null +++ b/docs/diagrams/TutorialFilterSequenceDiagram.puml @@ -0,0 +1,69 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":TutorialFilterCommandParser" as TutorialFilterCommandParser LOGIC_COLOR +participant ":TutorialFilterCommand" as TutorialFilterCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("tutorial filter g/T03") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("tutorial filter g/T03") +activate AddressBookParser + +create TutorialFilterCommandParser +AddressBookParser -> TutorialFilterCommandParser +activate TutorialFilterCommandParser + +TutorialFilterCommandParser --> AddressBookParser +deactivate TutorialFilterCommandParser + +AddressBookParser -> TutorialFilterCommandParser : parse("T03") +activate TutorialFilterCommandParser + +create TutorialFilterCommand +TutorialFilterCommandParser -> TutorialFilterCommand +activate TutorialFilterCommand + +TutorialFilterCommand --> TutorialFilterCommandParser +deactivate TutorialFilterCommand + +TutorialFilterCommandParser --> AddressBookParser +deactivate TutorialFilterCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +TutorialFilterCommandParser -[hidden]-> AddressBookParser +destroy TutorialFilterCommandParser + +AddressBookParser --> LogicManager +deactivate AddressBookParser + +LogicManager -> TutorialFilterCommand : execute() +activate TutorialFilterCommand + +TutorialFilterCommand -> Model : getFilteredStudentList() +activate Model + +Model -> TutorialFilterCommand: student list +deactivate Model + +create CommandResult +TutorialFilterCommand -> CommandResult +activate CommandResult + +CommandResult --> TutorialFilterCommand +deactivate CommandResult + +TutorialFilterCommand --> LogicManager : result +deactivate TutorialFilterCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/TutorialGroupClassDiagram.png b/docs/diagrams/TutorialGroupClassDiagram.png new file mode 100644 index 00000000000..19672e03c73 Binary files /dev/null and b/docs/diagrams/TutorialGroupClassDiagram.png differ diff --git a/docs/diagrams/TutorialGroupClassDiagram.puml b/docs/diagrams/TutorialGroupClassDiagram.puml new file mode 100644 index 00000000000..4f603772cf6 --- /dev/null +++ b/docs/diagrams/TutorialGroupClassDiagram.puml @@ -0,0 +1,21 @@ +@startuml +'https://plantuml.com/class-diagram + +class TutorialGroup{ ++ {static} String MESSAGE_CONSTRAINTS ++ {static} String VALIDATION_REGEX ++ {static} String DEFAULT_TUTORIAL_GROUP +- String tutorialGroup + ++ {static} boolean isValidTutorialGroup(String test) ++ addStudentToTutorialGroup(Student student) ++ ArrayList getStudents() ++ boolean isSameTutorialGroup(TutorialGroup group) +} + +class Student { +} + +TutorialGroup --> "*" Student + +@enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 95473d5aa19..3518849ccd9 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -10,11 +10,12 @@ Class "{abstract}\nUiPart" as UiPart Class UiManager Class MainWindow Class HelpWindow -Class ResultDisplay -Class PersonListPanel -Class PersonCard -Class StatusBarFooter -Class CommandBox +Class CommandOutput +Class CommandInput +Class StudentListPanel +Class StudentListCard +Class TaskListPanel +Class TaskListCard } package Model <> { @@ -30,31 +31,29 @@ HiddenOutside ..> Ui UiManager .left.|> Ui UiManager -down-> "1" MainWindow -MainWindow *-down-> "1" CommandBox -MainWindow *-down-> "1" ResultDisplay -MainWindow *-down-> "1" PersonListPanel -MainWindow *-down-> "1" StatusBarFooter +MainWindow *-down-> "1" CommandOutput +MainWindow *-down-> "1" CommandInput +MainWindow *-down-> "0..1" StudentListPanel +StudentListPanel *-down-> "*" StudentListCard +MainWindow *-down-> "0..1" TaskListPanel +TaskListPanel *-down-> "*" TaskListCard MainWindow --> "0..1" HelpWindow -PersonListPanel -down-> "*" PersonCard - -MainWindow -left-|> UiPart - -ResultDisplay --|> UiPart -CommandBox --|> UiPart -PersonListPanel --|> UiPart -PersonCard --|> UiPart -StatusBarFooter --|> UiPart +MainWindow --|> UiPart +CommandInput --|> UiPart +CommandOutput --|> UiPart HelpWindow --|> UiPart +StudentListPanel --|> UiPart +StudentListCard --|> UiPart +TaskListPanel --|> UiPart +TaskListCard --|> UiPart -PersonCard ..> Model +StudentListCard .right.> Model +TaskListCard .right.> Model UiManager -right-> Logic MainWindow -left-> Logic -PersonListPanel -[hidden]left- HelpWindow -HelpWindow -[hidden]left- CommandBox -CommandBox -[hidden]left- ResultDisplay -ResultDisplay -[hidden]left- StatusBarFooter +CommandInput -[hidden]left- CommandOutput +HelpWindow -[hidden]left- CommandOutput -MainWindow -[hidden]-|> UiPart @enduml diff --git a/docs/diagrams/UndoRedoState0.puml b/docs/diagrams/UndoRedoState0.puml index 96e30744d24..34885420931 100644 --- a/docs/diagrams/UndoRedoState0.puml +++ b/docs/diagrams/UndoRedoState0.puml @@ -15,6 +15,6 @@ State2 -[hidden]right-> State3 hide State2 hide State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State1 @end diff --git a/docs/diagrams/UndoRedoState1.puml b/docs/diagrams/UndoRedoState1.puml index 01fcb9b2b96..0e2c8c72d33 100644 --- a/docs/diagrams/UndoRedoState1.puml +++ b/docs/diagrams/UndoRedoState1.puml @@ -16,7 +16,7 @@ State2 -[hidden]right-> State3 hide State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State2 @end diff --git a/docs/diagrams/UndoRedoState2.puml b/docs/diagrams/UndoRedoState2.puml index bccc230a5d1..0ce7073e187 100644 --- a/docs/diagrams/UndoRedoState2.puml +++ b/docs/diagrams/UndoRedoState2.puml @@ -14,7 +14,7 @@ package States <> { State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State3 @end diff --git a/docs/diagrams/UndoRedoState3.puml b/docs/diagrams/UndoRedoState3.puml index ea29c9483e4..50bf43b3f34 100644 --- a/docs/diagrams/UndoRedoState3.puml +++ b/docs/diagrams/UndoRedoState3.puml @@ -14,7 +14,7 @@ package States <> { State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State2 @end diff --git a/docs/diagrams/UndoRedoState4.puml b/docs/diagrams/UndoRedoState4.puml index 1b784cece80..83cbe4c740c 100644 --- a/docs/diagrams/UndoRedoState4.puml +++ b/docs/diagrams/UndoRedoState4.puml @@ -14,7 +14,7 @@ package States <> { State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State2 @end diff --git a/docs/diagrams/UndoRedoState5.puml b/docs/diagrams/UndoRedoState5.puml index 88927be32bc..fc89dd99d2d 100644 --- a/docs/diagrams/UndoRedoState5.puml +++ b/docs/diagrams/UndoRedoState5.puml @@ -14,7 +14,7 @@ package States <> { State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 -class Pointer as "Current State" #FFFFF +class Pointer as "Current State" #FFFFFF Pointer -up-> State3 note right on link: State ab2 deleted. 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..c61e4a962b4 100644 Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png deleted file mode 100644 index 1ec62caa2a5..00000000000 Binary files a/docs/images/BetterModelClassDiagram.png and /dev/null differ diff --git a/docs/images/CommitActivityDiagram.png b/docs/images/CommitActivityDiagram.png deleted file mode 100644 index c08c13f5c8b..00000000000 Binary files a/docs/images/CommitActivityDiagram.png and /dev/null differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index fa327b39618..c16e2cd6495 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/GradeEditActivityDiagram.png b/docs/images/GradeEditActivityDiagram.png new file mode 100644 index 00000000000..9ad1f3f229a Binary files /dev/null and b/docs/images/GradeEditActivityDiagram.png differ diff --git a/docs/images/GradeEditSequenceDiagram.png b/docs/images/GradeEditSequenceDiagram.png new file mode 100644 index 00000000000..e72f8705080 Binary files /dev/null and b/docs/images/GradeEditSequenceDiagram.png differ diff --git a/docs/images/GradeKeyClassDiagram.png b/docs/images/GradeKeyClassDiagram.png new file mode 100644 index 00000000000..0b182bac6cf Binary files /dev/null and b/docs/images/GradeKeyClassDiagram.png differ diff --git a/docs/images/GradeMapClassDiagram.png b/docs/images/GradeMapClassDiagram.png new file mode 100644 index 00000000000..3807daba2e0 Binary files /dev/null and b/docs/images/GradeMapClassDiagram.png differ diff --git a/docs/images/GradeViewActivityDiagram.png b/docs/images/GradeViewActivityDiagram.png new file mode 100644 index 00000000000..ff6ffa25715 Binary files /dev/null and b/docs/images/GradeViewActivityDiagram.png differ diff --git a/docs/images/GradeViewSequenceDiagram.png b/docs/images/GradeViewSequenceDiagram.png new file mode 100644 index 00000000000..c15fcd4871a Binary files /dev/null and b/docs/images/GradeViewSequenceDiagram.png differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png index 9e9ba9f79e5..d81d070c129 100644 Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 04070af60d8..341d03ea333 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/ParserClasses.png b/docs/images/ParserClasses.png index e7b4c8880cd..94d2823c3f0 100644 Binary files a/docs/images/ParserClasses.png and b/docs/images/ParserClasses.png differ diff --git a/docs/images/ParserSequenceDiagram.png b/docs/images/ParserSequenceDiagram.png new file mode 100644 index 00000000000..6c35d57c751 Binary files /dev/null and b/docs/images/ParserSequenceDiagram.png differ diff --git a/docs/images/SortTask.png b/docs/images/SortTask.png new file mode 100644 index 00000000000..f8f16dedc7a Binary files /dev/null and b/docs/images/SortTask.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 2533a5c1af0..ed500d43a35 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/StudentEnrollActivityDiagram.png b/docs/images/StudentEnrollActivityDiagram.png new file mode 100644 index 00000000000..24e43a48813 Binary files /dev/null and b/docs/images/StudentEnrollActivityDiagram.png differ diff --git a/docs/images/TAA.png b/docs/images/TAA.png new file mode 100644 index 00000000000..4d3ba1370f3 Binary files /dev/null and b/docs/images/TAA.png differ diff --git a/docs/images/TaskClassDiagram.png b/docs/images/TaskClassDiagram.png new file mode 100644 index 00000000000..76dfcf445d0 Binary files /dev/null and b/docs/images/TaskClassDiagram.png differ diff --git a/docs/images/TaskEditActivityDiagram.png b/docs/images/TaskEditActivityDiagram.png new file mode 100644 index 00000000000..b8f03076dbb Binary files /dev/null and b/docs/images/TaskEditActivityDiagram.png differ diff --git a/docs/images/TaskEditSequenceDiagram.png b/docs/images/TaskEditSequenceDiagram.png new file mode 100644 index 00000000000..94bb51c7dc6 Binary files /dev/null and b/docs/images/TaskEditSequenceDiagram.png differ diff --git a/docs/images/TaskExpandActivityDiagram.png b/docs/images/TaskExpandActivityDiagram.png new file mode 100644 index 00000000000..58ef779d11c Binary files /dev/null and b/docs/images/TaskExpandActivityDiagram.png differ diff --git a/docs/images/TaskListCard.png b/docs/images/TaskListCard.png new file mode 100644 index 00000000000..5a098348e8b Binary files /dev/null and b/docs/images/TaskListCard.png differ diff --git a/docs/images/TutorialAddActivityDiagram.png b/docs/images/TutorialAddActivityDiagram.png new file mode 100644 index 00000000000..9b087df8b15 Binary files /dev/null and b/docs/images/TutorialAddActivityDiagram.png differ diff --git a/docs/images/TutorialFilterSequenceDiagram.png b/docs/images/TutorialFilterSequenceDiagram.png new file mode 100644 index 00000000000..0c366ec7a4f Binary files /dev/null and b/docs/images/TutorialFilterSequenceDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..a38f252c137 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..fccd8e20f5d 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UndoRedoState0.png b/docs/images/UndoRedoState0.png deleted file mode 100644 index 8f7538cd884..00000000000 Binary files a/docs/images/UndoRedoState0.png and /dev/null differ diff --git a/docs/images/UndoRedoState1.png b/docs/images/UndoRedoState1.png deleted file mode 100644 index df9908d0948..00000000000 Binary files a/docs/images/UndoRedoState1.png and /dev/null differ diff --git a/docs/images/UndoRedoState2.png b/docs/images/UndoRedoState2.png deleted file mode 100644 index 36519c1015b..00000000000 Binary files a/docs/images/UndoRedoState2.png and /dev/null differ diff --git a/docs/images/UndoRedoState3.png b/docs/images/UndoRedoState3.png deleted file mode 100644 index 19959d01712..00000000000 Binary files a/docs/images/UndoRedoState3.png and /dev/null differ diff --git a/docs/images/UndoRedoState4.png b/docs/images/UndoRedoState4.png deleted file mode 100644 index 4c623e4f2c5..00000000000 Binary files a/docs/images/UndoRedoState4.png and /dev/null differ diff --git a/docs/images/UndoRedoState5.png b/docs/images/UndoRedoState5.png deleted file mode 100644 index 84ad2afa6bd..00000000000 Binary files a/docs/images/UndoRedoState5.png and /dev/null differ diff --git a/docs/images/UndoSequenceDiagram.png b/docs/images/UndoSequenceDiagram.png deleted file mode 100644 index 6addcd3a8d9..00000000000 Binary files a/docs/images/UndoSequenceDiagram.png and /dev/null differ diff --git a/docs/images/default screen.png b/docs/images/default screen.png new file mode 100644 index 00000000000..d8f16df3f79 Binary files /dev/null and b/docs/images/default screen.png differ diff --git a/docs/images/gunbux.png b/docs/images/gunbux.png new file mode 100644 index 00000000000..9e24fa8d90e Binary files /dev/null and b/docs/images/gunbux.png differ diff --git a/docs/images/limweijun.png b/docs/images/limweijun.png new file mode 100644 index 00000000000..fc5bb60357d Binary files /dev/null and b/docs/images/limweijun.png differ diff --git a/docs/images/luyiting0913.png b/docs/images/luyiting0913.png new file mode 100644 index 00000000000..5f8e757a326 Binary files /dev/null and b/docs/images/luyiting0913.png differ diff --git a/docs/images/student list.png b/docs/images/student list.png new file mode 100644 index 00000000000..a3c3e6699a3 Binary files /dev/null and b/docs/images/student list.png differ diff --git a/docs/images/taa_main.png b/docs/images/taa_main.png new file mode 100644 index 00000000000..2c96d39980e Binary files /dev/null and b/docs/images/taa_main.png differ diff --git a/docs/images/task list.png b/docs/images/task list.png new file mode 100644 index 00000000000..581b3f74fed Binary files /dev/null and b/docs/images/task list.png differ diff --git a/docs/images/tensaida.png b/docs/images/tensaida.png new file mode 100644 index 00000000000..72cd15e9741 Binary files /dev/null and b/docs/images/tensaida.png differ diff --git a/docs/images/tutorial filter.png b/docs/images/tutorial filter.png new file mode 100644 index 00000000000..a862ab2b6db Binary files /dev/null and b/docs/images/tutorial filter.png differ diff --git a/docs/images/tutorial list.png b/docs/images/tutorial list.png new file mode 100644 index 00000000000..70fe6ceb31c Binary files /dev/null and b/docs/images/tutorial list.png differ diff --git a/docs/images/yeojunjie.png b/docs/images/yeojunjie.png new file mode 100644 index 00000000000..836ef399aad Binary files /dev/null and b/docs/images/yeojunjie.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..340723bede6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,18 +1,16 @@ --- layout: page -title: AddressBook Level-3 +title: Teaching Assistant Assistant --- -[![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) - +[![CI Status](https://github.com/AY2223S1-CS2103T-T13-1/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2223S1-CS2103T-T13-1/tp/actions) +[![codecov](https://codecov.io/gh/AY2223S1-CS2103T-T13-1/tp/branch/master/graph/badge.svg?token=GB1YKZLVSX)](https://codecov.io/gh/AY2223S1-CS2103T-T13-1/tp) ![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. +**Teching Assistant Assistant (TAA) is a desktop application for managing students and tasks for TAs.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). +* If you are interested in using TAA, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). +* If you are interested about developing TAA, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. **Acknowledgements** diff --git a/docs/team/gunbux.md b/docs/team/gunbux.md new file mode 100644 index 00000000000..a6a0f14b79d --- /dev/null +++ b/docs/team/gunbux.md @@ -0,0 +1,52 @@ +--- +layout: page +title: Chun Yu's Project Portfolio Page +--- + +### Project: Teaching Assistant Assistant + +Teaching Assistant Assistant (TAA) is a desktop app for Teaching Assistants (TAs). It keeps track of TAs’ students, +tutorial groups, and tasks. + + +Given below are my contributions to the project. + +* **Code contributed**: + * [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=gunbux&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-09-16&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + * [Created Issues/PRs](https://github.com/AY2223S1-CS2103T-T13-1/tp/issues?q=author%3Agunbux+) + +* **Enhancement Added**: + * **New Feature**: Added the tasks functionality and commands + * What it does: allows the user to add, delete, edit, and view tasks + * Using the current AB3 workflow was not possible as we needed to assign students to tasks without interacting +with the model. Instead, we had to create a new design for Tasks, where we delay the creation of the Task in the Parser +till we are able to search through the student list in model + * **New Feature**: Added mass actions for index based commands + * What it does: allows the user to perform an action on multiple students at once + * This was done by modifying how index-based commands were implemented, alongside some changes to the parser as well. + * Modified parser and commands to enable two word commands + * Modified list commands to have sensible outputs + +* **Documentation**: + * User Guide: + * Revamped User Guide to be more user friendly and intuitive [#212](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/212) + * Fixed Formatting issues [#136](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/136), + [#132](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/132), [#35](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/35) + + * Developer Guide: + * Added User Stories, User Profile and Value Proposition [#20](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/20) + * Update UML Diagrams [#54](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/54) + * Added DG Implementation details for Tasks and Mass Actions [#106](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/106), + * Overall formatting and delivery of DG [#257](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/257), [#246](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/246) + * Added Appendix for Manual Testing and Effort [#246](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/246) + +* **Contribution to team-based tasks** + * Helped set up git workflow and PRs: Github Projects, Issues, Squash commit workflows + * Manage release for 1.3 + +* **Reviewing/mentoring contributions**: + * Helped guide team members on Git workflows, resolving merge conflicts, and PRs + * Helped review numerous PRs and issues. [#64](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/64), [#102](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/102) + +* **Contributions beyond the project team**: + * [Reported bugs for PED](https://github.com/gunbux/ped/issues) 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/limweijun.md b/docs/team/limweijun.md new file mode 100644 index 00000000000..3618139f171 --- /dev/null +++ b/docs/team/limweijun.md @@ -0,0 +1,32 @@ +--- +layout: page +title: Lim Wei Jun's Project Portfolio Page +--- + +### Project: Teaching Assistant Assistant + +Teaching Assistant Assistant (TAA) is a desktop app for Teaching Assistants (TAs). It keeps track of TAs’ students, tutorial groups, and tasks. + +Given below are my contributions to the project. + +* **New Feature**: Student add command. +* **New Feature**: Student delete command. +* **New Feature**: Added the ability sort task automatically by its deadline. +* **New Feature**: Ability to expand the task card whenever its click in the GUI. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=limweijun&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-09-16&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + +* **Project management**: + * Created issues [#49](https://github.com/AY2223S1-CS2103T-T13-1/tp/issues/49), [#62](https://github.com/AY2223S1-CS2103T-T13-1/tp/issues/62), [#115](https://github.com/AY2223S1-CS2103T-T13-1/tp/issues/115), [#120](https://github.com/AY2223S1-CS2103T-T13-1/tp/issues/120), [#203](https://github.com/AY2223S1-CS2103T-T13-1/tp/issues/203), [#205](https://github.com/AY2223S1-CS2103T-T13-1/tp/issues/205) + +* **Documentation**: + * User Guide: + * Added documentation for the quickstart, student related and task related section [#75](https://github.com/AY2223S1-CS2103T-T13-1/tp/issues/75) + + * Developer Guide: + * Added documentation for the task sort feature [#105](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/105) + * Added task class diagram and tutorial group class diagram [#239](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/239) + +**Community**: + * Reported bugs and suggestions for other teams: [1](https://github.com/limweijun/ped/issues/1) [2](https://github.com/limweijun/ped/issues/2) [3](https://github.com/limweijun/ped/issues/3) [4](https://github.com/limweijun/ped/issues/4) [5](https://github.com/limweijun/ped/issues/5) [6](https://github.com/limweijun/ped/issues/6) + diff --git a/docs/team/luyiting0913.md b/docs/team/luyiting0913.md new file mode 100644 index 00000000000..8cfccd8184c --- /dev/null +++ b/docs/team/luyiting0913.md @@ -0,0 +1,53 @@ +--- +layout: page +title: Lu Yiting's Project Portfolio Page +--- + +### Project: Teaching Assistant Assistant + +Teaching Assistant Assistant (TAA) is a desktop app for Teaching Assistants (TAs). It keeps track of TAs’ students, tutorial groups, and tasks. + +Given below are my contributions to the project. + +- **Code contributed**: + + * [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=luyiting&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-09-16&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other) + * [Created Issues/PRs](https://github.com/AY2223S1-CS2103T-T13-1/tp/issues?q=author%3Aluyiting0913) + + +- **New Feature**: + - Student edit command + - Student list command + - Student enroll command + - Student expel command + - Tutorial group class + - Tutorial group add command + - Tutorial group delete command + - Tutorial group list command + - Tutorial group filter command + +- **Testing**: + - Student edit command test + - Student enroll command test + - Student expel command test + - Tutorial group class test + - Tutorial group add command test + - Tutorial group delete command test + + +- **Documentation**: + + - User Guide [#122](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/122) + - Add new features and command to UG [#102](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/102) + + - Developer Guide: + - Add user stories and user stories [#98](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/98) + - Update UML Diagrams [#130](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/130) + + - **Reviewing/mentoring contributions**: + - Help release for milestones and issues + - Help review pull requests and issues and resolve merge conflicts + + - **Contributions beyond the project team**: + * [Reported bugs for PED](https://github.com/LuYiting0913/ped/issues) + diff --git a/docs/team/tensaida.md b/docs/team/tensaida.md new file mode 100644 index 00000000000..23ff2b3f767 --- /dev/null +++ b/docs/team/tensaida.md @@ -0,0 +1,45 @@ +--- +layout: page +title: Devesh's Project Portfolio Page +--- + +### Project: Teaching Assistant Assistant + +Teaching Assistant Assistant (TAA) is a desktop app for Teaching Assistants (TAs). It keeps track of TAs’ students, +tutorial groups, and tasks. + + +Given below are my contributions to the project. + +* **Enhancement Added**: + * **New Feature**: Added the grade functionality and commands + * What it does: allows the user to view and edit grades + * Every student-task pair is associated with a grade. Therefore, it was natural to use to a map to implement +the grades feature. However, maps were not used in the AB3 codebase so it was challenging to incorporate a map into +TAA. Since the grade interacts with both the `Student` and `Task` classes, there were some unexpected behaviours which +had to be dealt with, particularly when tasks and students were edited (as these operations would result in the creation +of new `Student` and `Task` objects). + +* **Team-tasks**: + * Refactored variables, class names, etc. in the AB3 codebase to make semantic sense for TAA [#50](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/50) + * Set up [Kanban board](https://postimg.cc/BPbCmLT0/cb052c47) on Github Projects to track issues better + * Set up Milestone v1.1 and assigned issues. Also set up Milestone v1.4 + * Incorporated [Mockito](https://site.mockito.org/) into project to create mock classes faster in unit tests [#69](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/69/) + +* **Code contributed**: + * [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=tensaida&breakdown=true&sort=groupTitle&sortWithin=title&since=2022-09-16&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other&tabOpen=true&tabType=authorship&tabAuthor=tensaida&tabRepo=AY2223S1-CS2103T-T13-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + * [Created Issues/PRs](https://github.com/AY2223S1-CS2103T-T13-1/tp/issues?q=author%3Atensaida+) + +* **Documentation**: + * User Guide: + * Added documentation about the grade functionality [#117](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/117) + + * Developer Guide: + * Added documentation about the grade functionality [#231](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/231) + * Updated UML diagrams to reflect current iteration of TAA [#236](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/236) + +* **Reviewing/mentoring contributions**: + * PR Review: [#239](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/239), [#246](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/246), [#92](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/92), [#217](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/217) + +* **Contributions beyond the project team**: + * [Reported bugs for PED](https://github.com/tensaida/ped/issues) diff --git a/docs/team/yeojunjie.md b/docs/team/yeojunjie.md new file mode 100644 index 00000000000..138a055355c --- /dev/null +++ b/docs/team/yeojunjie.md @@ -0,0 +1,48 @@ +--- +layout: page +title: Yeo Jun Jie's Project Portfolio Page +--- + +### Project: Teaching Assistant Assistant + +Teaching Assistant Assistant (TAA) is a desktop app for Teaching Assistants (TAs). It keeps track of TAs’ students, +tutorial groups, and tasks. + +Given below are my contributions to the project. + +- **Code Contributed**: [RepoSense link](https://nus-cs2103-ay2223s1.github.io/tp-dashboard/?search=yeojunjie&breakdown=true) + +- **New Feature**: Clicking on a task in the graphical user interface shows more information about the task. + + - This feature was implemented in [pull request #100](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/). + + - This feature allows the Teaching Assistant to view the students for which the task has to be done without having to type in a command. + + - This feature was challenging to implement correctly because JavaFX behaves differently across platforms. This lead to bugs that I had to [fix](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/194). + + - I wrote about the implementation and design considerations of this feature in [this section](https://ay2223s1-cs2103t-t13-1.github.io/tp/DeveloperGuide.html#expanding-tasklistcard-feature) of our developer guide. + +- **Enhancements implemented**: I enhanced the user interface (UI) by simplifying the underlying code, and standardising margins between UI elements. + + - This was accomplished by using a minimal amount of the appropriate UI elements. + + - In [pull request #74](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/74), I reduced `CommandInput.fxml` from 22 lines of code down to 11 lines of code. + + - In [pull request #94](), I reduced `StudentCard.fxml` from 35 lines of code down to 21 lines of code. + +- **Documentation**: + + - User Guide: + + - In the early stages of development, I designed the [initial mock-up of the user interface](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/34). + + - During the dry run of the Practical Examinations, my team's user guide received eleven bug reports. I managed and resolved them in pull requests [#150](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/150), [#151](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/151), [#197](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/197), [#198](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/198), [#199](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/199), and [#200](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/200). + + - Developer Guide: + + - I updated the class diagrams in pull requests [#125](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/125) and [#131](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/131). + +- **Reviewing/mentoring contributions**: + + - I made sure that the user guide was worded clearly by carefully reviewing proposed changes to `UserGuide.md`. An example would be the suggestions I made to [pull request #124](https://github.com/AY2223S1-CS2103T-T13-1/tp/pull/124). + diff --git a/docs/tutorials/AddRemark.md b/docs/tutorials/AddRemark.md index 880c701042f..89c591c8cda 100644 --- a/docs/tutorials/AddRemark.md +++ b/docs/tutorials/AddRemark.md @@ -28,7 +28,7 @@ package seedu.address.logic.commands; import seedu.address.model.Model; /** - * Changes the remark of an existing person in the address book. + * Changes the remark of an existing student in the address book. */ public class RemarkCommand extends Command { @@ -65,8 +65,8 @@ Following the convention in other commands, we add relevant messages as constant ``` java public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Edits the remark of the person identified " - + "by the index number used in the last person listing. " + + ": Edits the remark of the student identified " + + "by the index number used in the last student listing. " + "Existing remark will be overwritten by the input.\n" + "Parameters: INDEX (must be a positive integer) " + "r/ [REMARK]\n" @@ -101,8 +101,8 @@ public class RemarkCommand extends Command { private final String remark; /** - * @param index of the person in the filtered person list to edit the remark - * @param remark of the person to be updated to + * @param index of the student in the filtered student list to edit the remark + * @param remark of the student to be updated to */ public RemarkCommand(Index index, String remark) { requireAllNonNull(index, remark); @@ -225,11 +225,11 @@ If you are stuck, check out the sample ## Add `Remark` to the model -Now that we have all the information that we need, let’s lay the groundwork for propagating the remarks added into the in-memory storage of person data. We achieve that by working with the `Person` model. Each field in a Person is implemented as a separate class (e.g. a `Name` object represents the person’s name). That means we should add a `Remark` class so that we can use a `Remark` object to represent a remark given to a person. +Now that we have all the information that we need, let’s lay the groundwork for propagating the remarks added into the in-memory storage of student data. We achieve that by working with the `Person` model. Each field in a Person is implemented as a separate class (e.g. a `Name` object represents the student’s name). That means we should add a `Remark` class so that we can use a `Remark` object to represent a remark given to a student. ### 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. @@ -240,9 +240,9 @@ Let’s change `RemarkCommand` and `RemarkCommandParser` to use the new `Remark` ## Add a placeholder element for remark to the UI -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. +Without getting too deep into `fxml`, let’s go on a 5 minute adventure to get some placeholder text to show up for each student. -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.StudentListCard`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-639834f1e05afe2276a86372adf0fe5f69314642c2d93cfa543d614ce5a76688). **`PersonCard.java`:** @@ -311,9 +311,9 @@ Just add [this one line of code!](https://github.com/se-edu/addressbook-level3/c **`PersonCard.java`:** ``` java -public PersonCard(Person person, int displayedIndex) { +public PersonCard(Person student, int displayedIndex) { //... - remark.setText(person.getRemark().value); + remark.setText(student.getRemark().value); } ``` @@ -340,28 +340,28 @@ save it with `Model#setPerson()`. List lastShownList = model.getFilteredPersonList(); 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 = new Person( - personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(), - personToEdit.getAddress(), remark, personToEdit.getTags()); + Person studentToEdit = lastShownList.get(index.getZeroBased()); + Person editedStudent = new Person( + studentToEdit.getName(), studentToEdit.getPhone(), studentToEdit.getEmail(), + studentToEdit.getAddress(), remark, studentToEdit.getTags()); - model.setPerson(personToEdit, editedPerson); + model.setPerson(studentToEdit, editedStudent); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(generateSuccessMessage(editedPerson)); + return new CommandResult(generateSuccessMessage(editedStudent)); } /** * Generates a command execution success message based on whether * the remark is added to or removed from - * {@code personToEdit}. + * {@code studentToEdit}. */ - private String generateSuccessMessage(Person personToEdit) { + private String generateSuccessMessage(Person studentToEdit) { String message = !remark.value.isEmpty() ? MESSAGE_ADD_REMARK_SUCCESS : MESSAGE_DELETE_REMARK_SUCCESS; - return String.format(message, personToEdit); + return String.format(message, studentToEdit); } ``` diff --git a/docs/tutorials/RemovingFields.md b/docs/tutorials/RemovingFields.md index f29169bc924..dcf0a51c618 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) @@ -100,7 +100,7 @@ In `src/test/data/`, data meant for testing purposes are stored. While keeping t ```json { - "persons": [ { + "students": [ { "name": "Person with invalid name field: Ha!ns Mu@ster", "phone": "9482424", "email": "hans@example.com", diff --git a/docs/tutorials/TracingCode.md b/docs/tutorials/TracingCode.md index 4fb62a83ef6..da28561f69a 100644 --- a/docs/tutorials/TracingCode.md +++ b/docs/tutorials/TracingCode.md @@ -189,22 +189,22 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ @Override public CommandResult execute(Model model) throws CommandException { ... - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { + Person studentToEdit = lastShownList.get(index.getZeroBased()); + Person editedStudent = createEditedPerson(studentToEdit, editStudentDescriptor); + if (!studentToEdit.isSamePerson(editedStudent) && model.hasPerson(editedStudent)) { throw new CommandException(MESSAGE_DUPLICATE_PERSON); } - model.setPerson(personToEdit, editedPerson); + model.setPerson(studentToEdit, editedStudent); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); + return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedStudent)); } ``` 1. As suspected, `command#execute()` does indeed make changes to the `model` object. Specifically, - * it uses the `setPerson()` method (defined in the interface `Model` and implemented in `ModelManager` as per the usual pattern) to update the person data. - * it uses the `updateFilteredPersonList` method to ask the `Model` to populate the 'filtered list' with _all_ persons.
- FYI, The 'filtered list' is the list of persons resulting from the most recent operation that will be shown to the user immediately after. For the `edit` command, we populate it with all the persons so that the user can see the edited person along with all other persons. If this was a `find` command, we would be setting that list to contain the search results instead.
- To provide some context, given below is the class diagram of the `Model` component. See if you can figure out where the 'filtered list' of persons is being tracked. + * it uses the `setPerson()` method (defined in the interface `Model` and implemented in `ModelManager` as per the usual pattern) to update the student data. + * it uses the `updateFilteredPersonList` method to ask the `Model` to populate the 'filtered list' with _all_ students.
+ FYI, The 'filtered list' is the list of students resulting from the most recent operation that will be shown to the user immediately after. For the `edit` command, we populate it with all the students so that the user can see the edited student along with all other students. If this was a `find` command, we would be setting that list to contain the search results instead.
+ To provide some context, given below is the class diagram of the `Model` component. See if you can figure out where the 'filtered list' of students is being tracked.
* :bulb: This may be a good time to read through the [`Model` component section of the DG](../DeveloperGuide.html#model-component) @@ -231,7 +231,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ * {@code JsonSerializableAddressBook}. */ public JsonSerializableAddressBook(ReadOnlyAddressBook source) { - persons.addAll( + students.addAll( source.getPersonList() .stream() .map(JsonAdaptedPerson::new) diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 4133aaa0151..49681276c9b 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -37,7 +37,6 @@ public class MainApp extends Application { public static final Version VERSION = new Version(0, 2, 0, true); - private static final Logger logger = LogsCenter.getLogger(MainApp.class); protected Ui ui; diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e469..ff9ce0ed1ea 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -7,7 +7,10 @@ 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_INVALID_TUTORIAL_GROUP_DISPLAYED_INDEX = + "The tutorial index provided is invalid"; + public static final String MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX = "The student index provided is invalid"; + public static final String MESSAGE_INVALID_TASK_DISPLAYED_INDEX = "The task index provided is invalid"; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; } diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..9841c7c9530 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -3,12 +3,16 @@ import java.nio.file.Path; import javafx.collections.ObservableList; +import javafx.collections.ObservableMap; import seedu.address.commons.core.GuiSettings; import seedu.address.logic.commands.CommandResult; 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.grade.Grade; +import seedu.address.model.grade.GradeKey; +import seedu.address.model.student.Student; +import seedu.address.model.task.Task; /** * API of the Logic component @@ -31,7 +35,10 @@ public interface Logic { ReadOnlyAddressBook getAddressBook(); /** Returns an unmodifiable view of the filtered list of persons */ - ObservableList getFilteredPersonList(); + ObservableList getFilteredStudentList(); + + /** Returns an unmodifiable view of the filtered list of tasks */ + ObservableList getFilteredTaskList(); /** * Returns the user prefs' address book file path. @@ -47,4 +54,6 @@ public interface Logic { * Set the user prefs' GUI settings. */ void setGuiSettings(GuiSettings guiSettings); + + ObservableMap getGradeMap(); } diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9d9c6d15bdc..effca8b377a 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -5,16 +5,20 @@ import java.util.logging.Logger; import javafx.collections.ObservableList; +import javafx.collections.ObservableMap; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; import seedu.address.logic.commands.Command; import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.TaaParser; 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.grade.Grade; +import seedu.address.model.grade.GradeKey; +import seedu.address.model.student.Student; +import seedu.address.model.task.Task; import seedu.address.storage.Storage; /** @@ -26,7 +30,7 @@ public class LogicManager implements Logic { private final Model model; private final Storage storage; - private final AddressBookParser addressBookParser; + private final TaaParser taaParser; /** * Constructs a {@code LogicManager} with the given {@code Model} and {@code Storage}. @@ -34,7 +38,7 @@ public class LogicManager implements Logic { public LogicManager(Model model, Storage storage) { this.model = model; this.storage = storage; - addressBookParser = new AddressBookParser(); + taaParser = new TaaParser(); } @Override @@ -42,7 +46,7 @@ public CommandResult execute(String commandText) throws CommandException, ParseE logger.info("----------------[USER COMMAND][" + commandText + "]"); CommandResult commandResult; - Command command = addressBookParser.parseCommand(commandText); + Command command = taaParser.parseCommand(commandText); commandResult = command.execute(model); try { @@ -60,8 +64,13 @@ public ReadOnlyAddressBook getAddressBook() { } @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); + public ObservableList getFilteredStudentList() { + return model.getFilteredStudentList(); + } + + @Override + public ObservableList getFilteredTaskList() { + return model.getFilteredTaskList(); } @Override @@ -78,4 +87,9 @@ public GuiSettings getGuiSettings() { public void setGuiSettings(GuiSettings guiSettings) { model.setGuiSettings(guiSettings); } + + @Override + public ObservableMap getGradeMap() { + return this.model.getGradeMap(); + } } diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java deleted file mode 100644 index 71656d7c5c8..00000000000 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ /dev/null @@ -1,67 +0,0 @@ -package seedu.address.logic.commands; - -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_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Adds a person to the address book. - */ -public class AddCommand extends Command { - - public static final String COMMAND_WORD = "add"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " - + "Parameters: " - + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + 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"; - - 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"; - - private final Person toAdd; - - /** - * Creates an AddCommand to add the specified {@code Person} - */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - - if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.addPerson(toAdd); - return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddCommand // instanceof handles nulls - && toAdd.equals(((AddCommand) other).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..58e08851dbf 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -10,7 +10,7 @@ */ public class ClearCommand extends Command { - public static final String COMMAND_WORD = "clear"; + public static final String COMMAND_WORD = "clear all"; public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java deleted file mode 100644 index 02fd256acba..00000000000 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ /dev/null @@ -1,53 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import seedu.address.commons.core.Messages; -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; - -/** - * Deletes a person identified using it's displayed index from the address book. - */ -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"; - - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; - - private final Index targetIndex; - - public DeleteCommand(Index targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof DeleteCommand // instanceof handles nulls - && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index 3dd85a8ba90..9c539e4018c 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -7,7 +7,7 @@ */ public class ExitCommand extends Command { - public static final String COMMAND_WORD = "exit"; + public static final String COMMAND_WORD = "bye bye"; public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java deleted file mode 100644 index d6b19b0a0de..00000000000 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ /dev/null @@ -1,42 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.commons.core.Messages; -import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case insensitive. - */ -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 " - + "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"; - - private final NameContainsKeywordsPredicate predicate; - - public FindCommand(NameContainsKeywordsPredicate predicate) { - this.predicate = predicate; - } - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(predicate); - return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof FindCommand // instanceof handles nulls - && predicate.equals(((FindCommand) other).predicate)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index bf824f91bd0..23f70ed4936 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -7,7 +7,7 @@ */ public class HelpCommand extends Command { - public static final String COMMAND_WORD = "help"; + public static final String COMMAND_WORD = "help me"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n" + "Example: " + COMMAND_WORD; diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java deleted file mode 100644 index 84be6ad2596..00000000000 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ /dev/null @@ -1,24 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import seedu.address.model.Model; - -/** - * Lists all persons in the address book to the user. - */ -public class ListCommand extends Command { - - public static final String COMMAND_WORD = "list"; - - public static final String MESSAGE_SUCCESS = "Listed all persons"; - - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/grade/GradeEditCommand.java b/src/main/java/seedu/address/logic/commands/grade/GradeEditCommand.java new file mode 100644 index 00000000000..e84ad93c5c8 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/grade/GradeEditCommand.java @@ -0,0 +1,150 @@ +package seedu.address.logic.commands.grade; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GRADE; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TASKS; + +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.grade.Grade; +import seedu.address.model.grade.GradeKey; +import seedu.address.model.student.Student; +import seedu.address.model.task.Task; + +/** + * Edits the grade of an existing student-task pair in the address book. + */ +public class GradeEditCommand extends Command { + public static final String COMMAND_WORD = "grade edit"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the grade of the student's task " + + "by the index number used in the displayed student list and task list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: STUDENT_INDEX TASK_INDEX (must be positive integers and NOT TOO BIG)" + + "[" + PREFIX_GRADE + "GRADE (T or F)]...\n" + + "Example: " + COMMAND_WORD + " 1 2 " + + PREFIX_GRADE + "T"; + public static final String MESSAGE_EDIT_GRADE_SUCCESS = "Grade %s of Task %s and Student %s"; + public static final String MESSAGE_NOT_EDITED = "T OR F must be provided"; + public static final String MESSAGE_STUDENT_TASK_PAIR_NOT_FOUND = "This student and task pair is not found."; + private final Index studentIndex; + private final Index taskIndex; + private final EditGradeDescriptor editGradeDescriptor; + /** + * @param studentIndex of the student in the filtered student list to edit + * @param taskIndex of the task in the filtered task list to edit + * @param editGradeDescriptor details to edit the grade with + */ + public GradeEditCommand(Index studentIndex, Index taskIndex, EditGradeDescriptor editGradeDescriptor) { + requireNonNull(studentIndex); + requireNonNull(taskIndex); + requireNonNull(editGradeDescriptor); + + this.studentIndex = studentIndex; + this.taskIndex = taskIndex; + this.editGradeDescriptor = new EditGradeDescriptor(editGradeDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownStudentList = model.getFilteredStudentList(); + List lastShownTaskList = model.getFilteredTaskList(); + + if (studentIndex.getZeroBased() >= lastShownStudentList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); + } else if (taskIndex.getZeroBased() >= lastShownTaskList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + Student studentGradeToEdit = lastShownStudentList.get(studentIndex.getZeroBased()); + Task taskGradeToEdit = lastShownTaskList.get(taskIndex.getZeroBased()); + if (!taskGradeToEdit.hasStudent(studentGradeToEdit)) { + throw new CommandException(MESSAGE_STUDENT_TASK_PAIR_NOT_FOUND); + } + Grade editedGrade = createEditedGrade(studentGradeToEdit, taskGradeToEdit, editGradeDescriptor); + model.addGrade(new GradeKey(studentGradeToEdit, taskGradeToEdit), editedGrade); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_PERSONS); + model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + return new CommandResult(String.format(MESSAGE_EDIT_GRADE_SUCCESS, editedGrade.name(), + taskGradeToEdit.getTaskName().taskName, studentGradeToEdit.getName())); + } + + /** + * Creates and returns a {@code Grade} with the details of {@code student} and {@code task} + * edited with {@code editGradeDescriptor}. + */ + private static Grade createEditedGrade(Student student, Task task, EditGradeDescriptor editGradeDescriptor) { + assert student != null; + assert task != null; + + return editGradeDescriptor.getGrade(); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof GradeEditCommand)) { + return false; + } + + // state check + GradeEditCommand e = (GradeEditCommand) other; + return this.studentIndex.equals(e.studentIndex) + && this.taskIndex.equals(e.taskIndex) + && this.editGradeDescriptor.equals(e.editGradeDescriptor); + } + + /** + * Stores the details to edit the grade with. + */ + public static class EditGradeDescriptor { + private Grade grade; + + public EditGradeDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code grade} is used internally. + */ + public EditGradeDescriptor(EditGradeDescriptor toCopy) { + setGrade(toCopy.grade); + } + + public void setGrade(Grade grade) { + this.grade = grade; + } + + public Grade getGrade() { + return this.grade; + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditGradeDescriptor)) { + return false; + } + + // state check + EditGradeDescriptor e = (EditGradeDescriptor) other; + + return getGrade().equals(e.getGrade()); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/grade/GradeViewCommand.java b/src/main/java/seedu/address/logic/commands/grade/GradeViewCommand.java new file mode 100644 index 00000000000..15d3c9557d0 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/grade/GradeViewCommand.java @@ -0,0 +1,85 @@ +package seedu.address.logic.commands.grade; + +import static java.util.Objects.requireNonNull; + +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.grade.Grade; +import seedu.address.model.grade.GradeKey; +import seedu.address.model.student.Student; +import seedu.address.model.task.Task; + +/** + * Edits the grade of an existing student-task pair in the address book. + */ +public class GradeViewCommand extends Command { + public static final String COMMAND_WORD = "grade view"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": View the grade of the student's task " + + "by the index number used in the displayed student list and task list.\n" + + "Parameters: STUDENT_INDEX TASK_INDEX (must be positive integers and NOT TOO BIG)" + + "\n" + + "Example: " + COMMAND_WORD + " 1 2"; + public static final String MESSAGE_VIEW_GRADE_SUCCESS = "Grade: %s"; + public static final String MESSAGE_STUDENT_TASK_PAIR_NOT_FOUND = "This student and task pair is not found."; + private final Index studentIndex; + private final Index taskIndex; + /** + * @param studentIndex of the student in the filtered student list to edit + * @param taskIndex of the task in the filtered task list to edit + */ + public GradeViewCommand(Index studentIndex, Index taskIndex) { + requireNonNull(studentIndex); + requireNonNull(taskIndex); + + this.studentIndex = studentIndex; + this.taskIndex = taskIndex; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownStudentList = model.getFilteredStudentList(); + List lastShownTaskList = model.getFilteredTaskList(); + + if (studentIndex.getZeroBased() >= lastShownStudentList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); + } else if (taskIndex.getZeroBased() >= lastShownTaskList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + Student studentGradeToEdit = lastShownStudentList.get(studentIndex.getZeroBased()); + Task taskGradeToEdit = lastShownTaskList.get(taskIndex.getZeroBased()); + if (!taskGradeToEdit.getStudents().contains(studentGradeToEdit)) { + throw new CommandException(MESSAGE_STUDENT_TASK_PAIR_NOT_FOUND); + } + Grade grade = model.getGradeMap().get(new GradeKey(studentGradeToEdit, taskGradeToEdit)); + if (grade == null) { + grade = Grade.UNGRADED; + } + return new CommandResult(String.format(MESSAGE_VIEW_GRADE_SUCCESS, grade.name())); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof GradeViewCommand)) { + return false; + } + + // state check + GradeViewCommand e = (GradeViewCommand) other; + return this.studentIndex.equals(e.studentIndex) + && this.taskIndex.equals(e.taskIndex); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/student/StudentAddCommand.java b/src/main/java/seedu/address/logic/commands/student/StudentAddCommand.java new file mode 100644 index 00000000000..776fe6b2aef --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/student/StudentAddCommand.java @@ -0,0 +1,72 @@ +package seedu.address.logic.commands.student; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +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.CliSyntax.PREFIX_TUTORIAL_GROUP; + +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.Student; +import seedu.address.model.student.TutorialGroup; + +/** + * Adds a student to the address book. + */ +public class StudentAddCommand extends Command { + public static final String COMMAND_WORD = "student add"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a student to the address book. " + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_TUTORIAL_GROUP + "TUTORIAL GROUP " + + PREFIX_PHONE + "PHONE " + + PREFIX_EMAIL + "EMAIL " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_TUTORIAL_GROUP + "T01 " + + PREFIX_PHONE + "98765432 " + + PREFIX_EMAIL + "johnd@example.com " + + PREFIX_TAG + "friends " + + PREFIX_TAG + "owesMoney"; + public static final String MESSAGE_SUCCESS = "New student added: %1$s"; + public static final String MESSAGE_DUPLICATE_PERSON = "This student already exists in the address book"; + public static final String MESSAGE_TUTORIAL_GROUP_NOT_FOUND = "This tutorial group is not found."; + private final Student toAdd; + + /** + * Creates an StudentAddCommand to add the specified {@code Student} + */ + public StudentAddCommand(Student student) { + requireNonNull(student); + toAdd = student; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + TutorialGroup toAddTG = toAdd.getTutorialGroup(); + + if (model.hasStudent(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + if (toAddTG != null && !model.hasTutorialGroup(toAddTG)) { + throw new CommandException(MESSAGE_TUTORIAL_GROUP_NOT_FOUND); + } + model.addStudent(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof StudentAddCommand // instanceof handles nulls + && toAdd.equals(((StudentAddCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/student/StudentDeleteCommand.java b/src/main/java/seedu/address/logic/commands/student/StudentDeleteCommand.java new file mode 100644 index 00000000000..44cc1b61a80 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/student/StudentDeleteCommand.java @@ -0,0 +1,65 @@ +package seedu.address.logic.commands.student; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.Arrays; +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.Student; + +/** + * Deletes a student identified using it's displayed index from the address book. + */ +public class StudentDeleteCommand extends Command { + + public static final String COMMAND_WORD = "student delete"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the student identified by the index number(s) (separated by whitespace)" + + "used in the displayed person list.\n" + + "Parameters: INDEX (must be a positive integer and NOT TOO BIG)\n" + + "Example: " + COMMAND_WORD + " 1 2 4"; + + public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Student: %1$s"; + + private final Index[] targetIndexes; + + public StudentDeleteCommand(Index[] targetIndexes) { + this.targetIndexes = targetIndexes; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredStudentList(); + ArrayList studentsToDelete = new ArrayList<>(); + + for (Index targetIndex : targetIndexes) { + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); + } + Student studentToDelete = lastShownList.get(targetIndex.getZeroBased()); + studentsToDelete.add(studentToDelete); + } + + for (Student student : studentsToDelete) { + model.deleteStudent(student); + } + + return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, studentsToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof StudentDeleteCommand // instanceof handles nulls + && Arrays.equals(this.targetIndexes, ((StudentDeleteCommand) other).targetIndexes)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/student/StudentEditCommand.java similarity index 53% rename from src/main/java/seedu/address/logic/commands/EditCommand.java rename to src/main/java/seedu/address/logic/commands/student/StudentEditCommand.java index 7e36114902f..c49f2b57514 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/student/StudentEditCommand.java @@ -1,11 +1,11 @@ -package seedu.address.logic.commands; +package seedu.address.logic.commands.student; 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_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.CliSyntax.PREFIX_TUTORIAL_GROUP; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; import java.util.Collections; @@ -17,89 +17,101 @@ import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; import seedu.address.commons.util.CollectionUtil; +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.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.Email; +import seedu.address.model.student.Name; +import seedu.address.model.student.Phone; +import seedu.address.model.student.Student; +import seedu.address.model.student.TutorialGroup; import seedu.address.model.tag.Tag; + + + + /** - * Edits the details of an existing person in the address book. + * Edits the details of an existing student in the address book. */ -public class EditCommand extends Command { +public class StudentEditCommand extends Command { - public static final String COMMAND_WORD = "edit"; + public static final String COMMAND_WORD = "student edit"; - 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. " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the student identified " + + "by the index number used in the displayed student list. " + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " + + "Parameters: INDEX (must be a positive integer and NOT TOO BIG) " + + "[" + PREFIX_TUTORIAL_GROUP + "TUTORIAL GROUP] " + "[" + PREFIX_NAME + "NAME] " + "[" + PREFIX_PHONE + "PHONE] " + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " + "[" + PREFIX_TAG + "TAG]...\n" + "Example: " + COMMAND_WORD + " 1 " + PREFIX_PHONE + "91234567 " + PREFIX_EMAIL + "johndoe@example.com"; - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; + public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Student: %1$s"; public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; + public static final String MESSAGE_DUPLICATE_PERSON = "This student already exists in the address book."; + public static final String MESSAGE_TUTORIAL_GROUP_NOT_FOUND = "This tutorial group is not found."; 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 index of the student in the filtered student list to edit + * @param editStudentDescriptor details to edit the student with */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { + public StudentEditCommand(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 studentToEdit = lastShownList.get(index.getZeroBased()); + Student editedStudent = createEditedStudent(studentToEdit, editStudentDescriptor); + + TutorialGroup toAddTG = editedStudent.getTutorialGroup(); - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { + if (!studentToEdit.isSameStudent(editedStudent) && model.hasStudent(editedStudent)) { throw new CommandException(MESSAGE_DUPLICATE_PERSON); } + if (toAddTG != null && !model.hasTutorialGroup(toAddTG)) { + throw new CommandException(MESSAGE_TUTORIAL_GROUP_NOT_FOUND); + } - model.setPerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); + model.setStudent(studentToEdit, editedStudent); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedStudent)); } /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. + * Creates and returns a {@code Student} with the details of {@code studentToEdit} + * edited with {@code editStudentDescriptor}. */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - 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()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + private static Student createEditedStudent(Student studentToEdit, EditStudentDescriptor editStudentDescriptor) { + assert studentToEdit != null; + + Name updatedName = editStudentDescriptor.getName().orElse(studentToEdit.getName()); + Phone updatedPhone = editStudentDescriptor.getPhone().orElse(studentToEdit.getPhone()); + Email updatedEmail = editStudentDescriptor.getEmail().orElse(studentToEdit.getEmail()); + Set updatedTags = editStudentDescriptor.getTags().orElse(studentToEdit.getTags()); + TutorialGroup updatedTutorialGroup = editStudentDescriptor.getTutorialGroup() + .orElse(studentToEdit.getTutorialGroup()); + return new Student(updatedName, updatedPhone, updatedEmail, updatedTags, updatedTutorialGroup); } @Override @@ -110,38 +122,38 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof EditCommand)) { + if (!(other instanceof StudentEditCommand)) { return false; } // state check - EditCommand e = (EditCommand) other; + StudentEditCommand e = (StudentEditCommand) 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. + * Stores the details to edit the student with. Each non-empty field value will replace the + * corresponding field value of the student. */ - public static class EditPersonDescriptor { + public static class EditStudentDescriptor { private Name name; private Phone phone; private Email email; - private Address address; + private TutorialGroup tutorialGroup; private Set tags; - public EditPersonDescriptor() {} + public EditStudentDescriptor() {} /** * Copy constructor. - * A defensive copy of {@code tags} is used internally. + * A defensive copy of {@code student} is used internally. */ - public EditPersonDescriptor(EditPersonDescriptor toCopy) { + public EditStudentDescriptor(EditStudentDescriptor toCopy) { setName(toCopy.name); setPhone(toCopy.phone); setEmail(toCopy.email); - setAddress(toCopy.address); + setTutorialGroup(toCopy.tutorialGroup); setTags(toCopy.tags); } @@ -149,7 +161,7 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) { * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); + return CollectionUtil.isAnyNonNull(name, phone, email, tags, tutorialGroup); } public void setName(Name name) { @@ -176,12 +188,12 @@ public Optional getEmail() { return Optional.ofNullable(email); } - public void setAddress(Address address) { - this.address = address; + public void setTutorialGroup(TutorialGroup tutorialGroup) { + this.tutorialGroup = tutorialGroup; } - public Optional
getAddress() { - return Optional.ofNullable(address); + public Optional getTutorialGroup() { + return Optional.ofNullable(tutorialGroup); } /** @@ -209,17 +221,16 @@ 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()) && getEmail().equals(e.getEmail()) - && getAddress().equals(e.getAddress()) && getTags().equals(e.getTags()); } } diff --git a/src/main/java/seedu/address/logic/commands/student/StudentEnrollCommand.java b/src/main/java/seedu/address/logic/commands/student/StudentEnrollCommand.java new file mode 100644 index 00000000000..9832680924f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/student/StudentEnrollCommand.java @@ -0,0 +1,206 @@ +package seedu.address.logic.commands.student; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TUTORIAL_GROUP; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +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.Email; +import seedu.address.model.student.Name; +import seedu.address.model.student.Phone; +import seedu.address.model.student.Student; +import seedu.address.model.student.TutorialGroup; +import seedu.address.model.tag.Tag; + +/** + * Edits the details of an existing student in the address book. + */ +public class StudentEnrollCommand extends Command { + + public static final String COMMAND_WORD = "student enroll"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Enroll the student identified to the given tutorial " + + "by the index number used in the displayed student list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer and NOT TOO BIG) " + + PREFIX_TUTORIAL_GROUP + "TUTORIAL GROUP "; + + public static final String MESSAGE_ENROLL_PERSON_SUCCESS = "Enrolled Student to: %1$s "; + public static final String MESSAGE_TUTORIAL_GROUP_NOT_FOUND = "This tutorial group is not found."; + public static final String MESSAGE_TUTORIAL_GROUP_ALREADY_ENROLLED = "This student is already enrolled in this " + + "tutorial group."; + public static final String MESSAGE_NOT_EDITED = "Tutorial group not edited."; + public static final String MESSAGE_STUDENT_ALREADY_ENROLLED = "Student already enrolled in a tutorial: %1$s." + + "Expel him first then enroll."; + + private final Index index; + private final EditStudentDescriptor editStudentDescriptor; + + /** + * @param index of the student in the filtered student list to edit + * @param editStudentDescriptor details to edit the student with + */ + public StudentEnrollCommand(Index index, EditStudentDescriptor editStudentDescriptor) { + requireNonNull(index); + requireNonNull(editStudentDescriptor); + + this.index = index; + + this.editStudentDescriptor = new EditStudentDescriptor(editStudentDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredStudentList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); + } + + Student studentToEdit = lastShownList.get(index.getZeroBased()); + Student editedStudent = createEditedStudent(studentToEdit, editStudentDescriptor); + + if (!model.hasTutorialGroup(editedStudent.getTutorialGroup())) { + throw new CommandException(MESSAGE_TUTORIAL_GROUP_NOT_FOUND); + } + + if (studentToEdit.belongsTo(editedStudent.getTutorialGroup())) { + throw new CommandException(MESSAGE_TUTORIAL_GROUP_ALREADY_ENROLLED); + } + + if (studentToEdit.isEnrolledInTutorial()) { + throw new CommandException(String.format(MESSAGE_STUDENT_ALREADY_ENROLLED, + studentToEdit.getTutorialGroup())); + } + + model.setStudent(studentToEdit, editedStudent); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(String.format(MESSAGE_ENROLL_PERSON_SUCCESS, editedStudent.getTutorialGroup())); + } + + /** + * Creates and returns a {@code Student} with the details of {@code studentToEdit} + * edited with {@code editStudentDescriptor}. + */ + private static Student createEditedStudent(Student studentToEdit, EditStudentDescriptor editStudentDescriptor) { + assert studentToEdit != null; + + Name updatedName = studentToEdit.getName(); + Phone updatedPhone = studentToEdit.getPhone(); + Email updatedEmail = studentToEdit.getEmail(); + Set updatedTags = studentToEdit.getTags(); + TutorialGroup updatedTutorialGroup = editStudentDescriptor.getTutorialGroup() + .orElse(studentToEdit.getTutorialGroup()); + return new Student(updatedName, updatedPhone, updatedEmail, updatedTags, updatedTutorialGroup); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof StudentEnrollCommand)) { + return false; + } + + // state check + StudentEnrollCommand e = (StudentEnrollCommand) other; + return index.equals(e.index) + && editStudentDescriptor.equals(e.editStudentDescriptor); + } + + /** + * Stores the details to edit the student with. Each non-empty field value will replace the + * corresponding field value of the person. + */ + public static class EditStudentDescriptor { + private TutorialGroup tutorialGroup; + private Name name; + private Phone phone; + private Email email; + + public EditStudentDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tutorialGroup} is used internally. + */ + public EditStudentDescriptor(EditStudentDescriptor toCopy) { + setTutorialGroup(toCopy.tutorialGroup); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(tutorialGroup); + } + + + public void setTutorialGroup(TutorialGroup tutorialGroup) { + this.tutorialGroup = tutorialGroup; + } + + public Optional getTutorialGroup() { + return Optional.ofNullable(tutorialGroup); + } + + public void setName(Name name) { + this.name = name; + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public void setPhone(Phone phone) { + this.phone = phone; + } + + public Optional getPhone() { + return Optional.ofNullable(phone); + } + + public void setEmail(Email email) { + this.email = email; + } + + public Optional getEmail() { + return Optional.ofNullable(email); + } + + + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditStudentDescriptor)) { + return false; + } + + // state check + EditStudentDescriptor e = (EditStudentDescriptor) other; + + return getTutorialGroup().equals(e.getTutorialGroup()); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/student/StudentExpelCommand.java b/src/main/java/seedu/address/logic/commands/student/StudentExpelCommand.java new file mode 100644 index 00000000000..c9ce0497a18 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/student/StudentExpelCommand.java @@ -0,0 +1,202 @@ +package seedu.address.logic.commands.student; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TUTORIAL_GROUP; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +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.Email; +import seedu.address.model.student.Name; +import seedu.address.model.student.Phone; +import seedu.address.model.student.Student; +import seedu.address.model.student.TutorialGroup; +import seedu.address.model.tag.Tag; + + + + +/** + * Edits the details of an existing student in the address book. + */ +public class StudentExpelCommand extends Command { + + public static final String COMMAND_WORD = "student expel"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Expel the student identified to the given tutorial " + + "by the index number used in the displayed student list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer and NOT TOO BIG) " + + PREFIX_TUTORIAL_GROUP + "TUTORIAL GROUP "; + + public static final String MESSAGE_EXPEL_PERSON_SUCCESS = "Expelled Student from: %1$s "; + public static final String MESSAGE_DUPLICATE_PERSON = "This student already exists in the address book."; + public static final String MESSAGE_TUTORIAL_GROUP_NOT_FOUND = "This tutorial group is not found."; + public static final String MESSAGE_NOT_EDITED = "Tutorial group not edited."; + public static final String MESSAGE_DOES_NOT_BELONG_TO_THIS_GROUP = "The student does not belong to this group"; + public static final String MESSAGE_TUTORIAL_NOT_INITIATED = "Tutorial group not initiated for this student. " + + "Cannot expel the student."; + + private final Index index; + private final EditStudentDescriptor editStudentDescriptor; + + /** + * @param index of the student in the filtered person list to edit + * @param editStudentDescriptor details to edit the student with + */ + public StudentExpelCommand(Index index, EditStudentDescriptor editStudentDescriptor) { + requireNonNull(index); + requireNonNull(editStudentDescriptor); + + this.index = index; + this.editStudentDescriptor = new EditStudentDescriptor(editStudentDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredStudentList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_STUDENT_DISPLAYED_INDEX); + } + + Student studentToEdit = lastShownList.get(index.getZeroBased()); + Student editedStudent = createEditedStudent(studentToEdit, editStudentDescriptor); + TutorialGroup originalGroup = editedStudent.getTutorialGroup(); + + if (!studentToEdit.isSameStudent(editedStudent) && model.hasStudent(editedStudent)) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + if (!model.hasTutorialGroup(originalGroup)) { + throw new CommandException(MESSAGE_TUTORIAL_GROUP_NOT_FOUND); + } + + if (!studentToEdit.isEnrolledInTutorial()) { + throw new CommandException(MESSAGE_TUTORIAL_NOT_INITIATED); + } + + if (!studentToEdit.belongsTo(originalGroup)) { + throw new CommandException(MESSAGE_DOES_NOT_BELONG_TO_THIS_GROUP); + } + + editedStudent = createExpelledStudent(studentToEdit, editStudentDescriptor); + + model.setStudent(studentToEdit, editedStudent); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(String.format(MESSAGE_EXPEL_PERSON_SUCCESS, originalGroup)); + } + + /** + * Creates and returns a {@code Student} with the details of {@code studentToEdit} + * edited with {@code editStudentDescriptor}. + */ + private static Student createEditedStudent(Student studentToEdit, EditStudentDescriptor editStudentDescriptor) { + assert studentToEdit != null; + + Name updatedName = studentToEdit.getName(); + Phone updatedPhone = studentToEdit.getPhone(); + Email updatedEmail = studentToEdit.getEmail(); + Set updatedTags = studentToEdit.getTags(); + TutorialGroup updatedTutorialGroup = editStudentDescriptor.getTutorialGroup() + .orElse(studentToEdit.getTutorialGroup()); + return new Student(updatedName, updatedPhone, updatedEmail, updatedTags, updatedTutorialGroup); + } + + private static Student createExpelledStudent(Student studentToEdit, EditStudentDescriptor editStudentDescriptor) { + assert studentToEdit != null; + + Name updatedName = studentToEdit.getName(); + Phone updatedPhone = studentToEdit.getPhone(); + Email updatedEmail = studentToEdit.getEmail(); + Set updatedTags = studentToEdit.getTags(); + TutorialGroup updatedTutorialGroup = null; + return new Student(updatedName, updatedPhone, updatedEmail, updatedTags, updatedTutorialGroup); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof StudentExpelCommand)) { + return false; + } + + // state check + StudentExpelCommand e = (StudentExpelCommand) other; + return index.equals(e.index) + && editStudentDescriptor.equals(e.editStudentDescriptor); + } + + /** + * Stores the details to edit the student with. Each non-empty field value will replace the + * corresponding field value of the student. + */ + public static class EditStudentDescriptor { + private TutorialGroup tutorialGroup; + + public EditStudentDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code tutorialGroup} is used internally. + */ + public EditStudentDescriptor(EditStudentDescriptor toCopy) { + setTutorialGroup(toCopy.tutorialGroup); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(tutorialGroup); + } + + public void setTutorialGroup(TutorialGroup tutorialGroup) { + this.tutorialGroup = tutorialGroup; + } + + /** + * Resets tutorial group. + */ + public void resetTutorialGroup(TutorialGroup tutorialGroup) { + this.tutorialGroup = tutorialGroup; + } + + public Optional getTutorialGroup() { + return Optional.ofNullable(tutorialGroup); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditStudentDescriptor)) { + return false; + } + + // state check + EditStudentDescriptor e = (EditStudentDescriptor) other; + + return getTutorialGroup().equals(e.getTutorialGroup()); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/student/StudentListCommand.java b/src/main/java/seedu/address/logic/commands/student/StudentListCommand.java new file mode 100644 index 00000000000..f33f4f01901 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/student/StudentListCommand.java @@ -0,0 +1,36 @@ +package seedu.address.logic.commands.student; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_STUDENTS; + +import java.util.List; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; +import seedu.address.model.student.Student; + +/** + * Lists all students in the address book to the user. + */ +public class StudentListCommand extends Command { + + public static final String COMMAND_WORD = "student list"; + + public static final String MESSAGE_SUCCESS = "Listed all students"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_STUDENTS); + List students = model.getFilteredStudentList(); + String display = ""; + + for (int i = 0; i < students.size(); i++) { + display += students.get(i).toString() + "\n"; + } + + return new CommandResult(display + MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/student/StudentResetFilterCommand.java b/src/main/java/seedu/address/logic/commands/student/StudentResetFilterCommand.java new file mode 100644 index 00000000000..580ee8b21c8 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/student/StudentResetFilterCommand.java @@ -0,0 +1,46 @@ +package seedu.address.logic.commands.student; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.List; + +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.Student; + +/** + * Resets any filter applied to student list. + */ +public class StudentResetFilterCommand extends Command { + public static final String COMMAND_WORD = "student unfilter"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Revert back to unfiltered state."; + + public static final String MESSAGE_SUCCESS = "Students filtered in this tutorial group: %1$s"; + public static final String MESSAGE_DUPLICATE_PERSON = "This task already exists in the address book"; + private List students; + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + students = model.getFilteredStudentList(); + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_PERSONS); + String result = ""; + for (int i = 0; i < students.size(); i++) { + Student student = students.get(i); + result += student + "\n"; + } + return new CommandResult(String.format(MESSAGE_SUCCESS) + "\n" + result); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof StudentResetFilterCommand // instanceof handles nulls + && students.equals(((StudentResetFilterCommand) other).students)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/task/TaskAddCommand.java b/src/main/java/seedu/address/logic/commands/task/TaskAddCommand.java new file mode 100644 index 00000000000..9454cec175f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/task/TaskAddCommand.java @@ -0,0 +1,103 @@ +package seedu.address.logic.commands.task; + +import static java.util.Objects.isNull; +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_DESC; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_STUDENT; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +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.Student; +import seedu.address.model.task.Task; +import seedu.address.model.task.TaskDeadline; +import seedu.address.model.task.TaskDescription; +import seedu.address.model.task.TaskName; + +/** + * Adds a task to the address book. + */ +public class TaskAddCommand extends Command { + + public static final String COMMAND_WORD = "task add"; + + // TODO: Update help once student is properly implemented + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a task to the address book. " + + "Parameters: " + + PREFIX_TASK_NAME + "TASK NAME " + + PREFIX_TASK_DESC + "TASK DESCRIPTION " + + PREFIX_TASK_DEADLINE + "TASK DEADLINE " + // + "[" + PREFIX_TASK_STUDENT + "STUDENT]...\n" + + PREFIX_TASK_STUDENT + "STUDENT\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_TASK_NAME + "Assignment 1 " + + PREFIX_TASK_DESC + "Complete assignment 1 " + + PREFIX_TASK_DEADLINE + "10/10/2022 " + + PREFIX_TASK_STUDENT + "John Doe"; + + public static final String MESSAGE_SUCCESS = "New task added: %1$s"; + public static final String MESSAGE_DUPLICATE_TASK = "This task already exists in the address book"; + public static final String MESSAGE_INVALID_STUDENT = "This student does not exist in the address book"; + + private final TaskName taskName; + private final TaskDescription taskDescription; + private final TaskDeadline taskDeadline; + private final List studentNames; + + /** + * Creates an TaskAddCommand to add the specified {@code Task} + */ + public TaskAddCommand(TaskName taskName, TaskDescription taskDesc, TaskDeadline taskDeadline, + List studentNames) { + requireNonNull(taskName); + requireNonNull(taskDesc); + requireNonNull(taskDeadline); + requireNonNull(studentNames); + this.taskName = taskName; + this.taskDescription = taskDesc; + this.taskDeadline = taskDeadline; + this.studentNames = studentNames; + } + + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + requireNonNull(studentNames); + + Set students = new HashSet<>(); + for (String studentName : studentNames) { + if (isNull(model.findStudent(studentName))) { + throw new CommandException(MESSAGE_INVALID_STUDENT); + } + + students.add(model.findStudent(studentName)); + } + + Task toAdd = new Task(taskName, taskDescription, taskDeadline, students); + + if (model.hasTask(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_TASK); + } + + model.addTask(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TaskAddCommand // instanceof handles nulls + && taskName.equals(((TaskAddCommand) other).taskName) + && taskDescription.equals(((TaskAddCommand) other).taskDescription) + && taskDeadline.equals(((TaskAddCommand) other).taskDeadline) + && studentNames.equals(((TaskAddCommand) other).studentNames)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/task/TaskDeleteCommand.java b/src/main/java/seedu/address/logic/commands/task/TaskDeleteCommand.java new file mode 100644 index 00000000000..1dfa6c75e9e --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/task/TaskDeleteCommand.java @@ -0,0 +1,64 @@ +package seedu.address.logic.commands.task; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.Arrays; +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.task.Task; + +/** + * Deletes a task identified using it's displayed index from the address book. + */ +public class TaskDeleteCommand extends Command { + + public static final String COMMAND_WORD = "task delete"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the task identified by the index numbers (seperated by whitespace)" + + " used in the displayed person list.\n" + + "Parameters: INDEX (must be a positive integer and NOT TOO BIG)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_DELETE_TASK_SUCCESS = "Deleted Task: %1$s"; + + private final Index[] targetIndexes; + + public TaskDeleteCommand(Index[] targetIndexes) { + this.targetIndexes = targetIndexes; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredTaskList(); + ArrayList tasksToDelete = new ArrayList<>(); + + for (Index targetIndex : targetIndexes) { + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + Task taskToDelete = lastShownList.get(targetIndex.getZeroBased()); + tasksToDelete.add(taskToDelete); + } + + for (Task task : tasksToDelete) { + model.deleteTask(task); + } + return new CommandResult(String.format(MESSAGE_DELETE_TASK_SUCCESS, tasksToDelete)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TaskDeleteCommand // instanceof handles nulls + && Arrays.equals(this.targetIndexes, ((TaskDeleteCommand) other).targetIndexes)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/task/TaskEditCommand.java b/src/main/java/seedu/address/logic/commands/task/TaskEditCommand.java new file mode 100644 index 00000000000..b2dab6d87dc --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/task/TaskEditCommand.java @@ -0,0 +1,230 @@ +package seedu.address.logic.commands.task; + +import static java.util.Objects.isNull; +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.commands.task.TaskAddCommand.MESSAGE_INVALID_STUDENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_DESC; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_STUDENT; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TASKS; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +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.Student; +import seedu.address.model.task.Task; +import seedu.address.model.task.TaskDeadline; +import seedu.address.model.task.TaskDescription; +import seedu.address.model.task.TaskName; + +/** + * Edits the details of an existing task in the address book. + */ +public class TaskEditCommand extends Command { + + public static final String COMMAND_WORD = "task edit"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the task 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 and NOT TOO BIG) " + + "[" + PREFIX_TASK_NAME + "TASK NAME] " + + "[" + PREFIX_TASK_DESC + "TASK DESCRIPTION] " + + "[" + PREFIX_TASK_DEADLINE + "TASK DEADLINE] " + + "[" + PREFIX_TASK_STUDENT + "STUDENT]...\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_TASK_NAME + "Assignment 1 " + + PREFIX_TASK_DESC + "Complete assignment 1 " + + PREFIX_TASK_DEADLINE + "2021-10-10"; + + public static final String MESSAGE_EDIT_TASK_SUCCESS = "Edited Task: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_TASK = "This task already exists in the address book."; + + private final Index index; + private final EditTaskDescriptor editTaskDescriptor; + + /** + * @param index of the task in the filtered task list to edit + * @param editTaskDescriptor details to edit the task with + */ + public TaskEditCommand(Index index, EditTaskDescriptor editTaskDescriptor) { + requireNonNull(index); + requireNonNull(editTaskDescriptor); + + this.index = index; + this.editTaskDescriptor = new EditTaskDescriptor(editTaskDescriptor); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredTaskList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + Task taskToEdit = lastShownList.get(index.getZeroBased()); + System.out.println("---"); + Task editedTask = createEditedTask(taskToEdit, editTaskDescriptor, model); + + if (!taskToEdit.isSameTask(editedTask) && model.hasTask(editedTask)) { + throw new CommandException(MESSAGE_DUPLICATE_TASK); + } + + model.setTask(taskToEdit, editedTask); + model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + return new CommandResult(String.format(MESSAGE_EDIT_TASK_SUCCESS, editedTask)); + + } + + /** + * Creates and returns a {@code Task} with the details of {@code taskToEdit} + * edited with {@code editTaskDescriptor}. + */ + private static Task createEditedTask( + Task taskToEdit, EditTaskDescriptor editTaskDescriptor, Model model + ) throws CommandException { + assert taskToEdit != null; + + TaskName updatedTaskName = editTaskDescriptor.getTaskName().orElse(taskToEdit.getTaskName()); + TaskDescription updatedTaskDescription = editTaskDescriptor.getTaskDescription() + .orElse(taskToEdit.getTaskDescription()); + TaskDeadline updatedTaskDeadline = editTaskDescriptor.getTaskDeadline().orElse(taskToEdit.getTaskDeadline()); + + Set students = new HashSet<>(taskToEdit.getStudents()); + + if (editTaskDescriptor.getStudentNames() != null) { + for (String studentName : editTaskDescriptor.getStudentNames()) { + if (isNull(model.findStudent(studentName))) { + throw new CommandException(MESSAGE_INVALID_STUDENT); + } + + students.add(model.findStudent(studentName)); + } + } + Task newTask = new Task(updatedTaskName, updatedTaskDescription, updatedTaskDeadline, students); + model.updateGrades(taskToEdit, newTask); + return newTask; + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof TaskEditCommand)) { + return false; + } + + // state check + TaskEditCommand e = (TaskEditCommand) other; + return index.equals(e.index) + && editTaskDescriptor.equals(e.editTaskDescriptor); + } + + /** + * Stores the details to edit the task with. Each non-empty field value will replace the + * corresponding field value of the task. + */ + public static class EditTaskDescriptor { + private TaskName taskName; + private TaskDescription taskDescription; + private TaskDeadline taskDeadline; + private List studentNames; + + + public EditTaskDescriptor() { + } + + /** + * Copy constructor. + * A defensive copy of {@code task} is used internally. + */ + public EditTaskDescriptor(EditTaskDescriptor toCopy) { + setTaskName(toCopy.taskName); + setTaskDescription(toCopy.taskDescription); + setTaskDeadline(toCopy.taskDeadline); + setStudentNames(toCopy.studentNames); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(taskName, taskDescription, taskDeadline, studentNames); + } + + public void setTaskName(TaskName taskName) { + this.taskName = taskName; + } + + public Optional getTaskName() { + return Optional.ofNullable(taskName); + } + + public void setTaskDescription(TaskDescription taskDescription) { + this.taskDescription = taskDescription; + } + + public Optional getTaskDescription() { + return Optional.ofNullable(taskDescription); + } + + public void setTaskDeadline(TaskDeadline taskDeadline) { + this.taskDeadline = taskDeadline; + } + + public Optional getTaskDeadline() { + return Optional.ofNullable(taskDeadline); + } + + /** + * Sets {@code students} to this object's {@code students}. + * A defensive copy of {@code students} is used internally. + */ + public void setStudentNames(List students) { + this.studentNames = (students != null) ? new ArrayList<>(students) : null; + } + + public List getStudentNames() { + return studentNames; + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditTaskDescriptor)) { + return false; + } + + // state check + EditTaskDescriptor e = (EditTaskDescriptor) other; + + return getTaskName().equals(e.getTaskName()) + && getTaskDescription().equals(e.getTaskDescription()) + && getTaskDeadline().equals(e.getTaskDeadline()) + && getStudentNames().equals(e.getStudentNames()); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/task/TaskListCommand.java b/src/main/java/seedu/address/logic/commands/task/TaskListCommand.java new file mode 100644 index 00000000000..01a2fb5d00e --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/task/TaskListCommand.java @@ -0,0 +1,38 @@ +package seedu.address.logic.commands.task; + + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TASKS; + +import java.util.List; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; +import seedu.address.model.task.Task; + + +/** + * Lists all tasks in the address book to the user. + */ +public class TaskListCommand extends Command { + + public static final String COMMAND_WORD = "task list"; + + public static final String MESSAGE_SUCCESS = "Listed all tasks"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + List tasks = model.getFilteredTaskList(); + String display = ""; + + for (int i = 0; i < tasks.size(); i++) { + display += String.valueOf(i + 1) + ". " + tasks.get(i).toString() + "\n"; + } + + return new CommandResult(display + MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/tutorialgroup/TutorialGroupAddCommand.java b/src/main/java/seedu/address/logic/commands/tutorialgroup/TutorialGroupAddCommand.java new file mode 100644 index 00000000000..7ba65ef99ef --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/tutorialgroup/TutorialGroupAddCommand.java @@ -0,0 +1,54 @@ +package seedu.address.logic.commands.tutorialgroup; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TUTORIAL_GROUP; +//import static seedu.address.logic.parser.CliSyntax.*; + +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.TutorialGroup; + +/** + * Adds a tutorial group to the address book. + */ +public class TutorialGroupAddCommand extends Command { + public static final String COMMAND_WORD = "tutorial add"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a tutorial group to the address book. " + + "Parameters: " + PREFIX_TUTORIAL_GROUP + "Txx"; + + public static final String MESSAGE_SUCCESS = "New tutorial group added: %1$s"; + public static final String MESSAGE_DUPLICATE_TUTORIAL_GROUP = "This tutorial group" + + " already exists in the address book"; + + private final TutorialGroup toAdd; + + /** + * Creates an TutorialGroupAddCommand to add the specified {@code TutorialGroup} + */ + public TutorialGroupAddCommand(TutorialGroup tutorialGroup) { + requireNonNull(tutorialGroup); + toAdd = tutorialGroup; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.hasTutorialGroup(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_TUTORIAL_GROUP); + } + + model.addTutorialGroup(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TutorialGroupAddCommand // instanceof handles nulls + && toAdd.equals(((TutorialGroupAddCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/tutorialgroup/TutorialGroupDeleteCommand.java b/src/main/java/seedu/address/logic/commands/tutorialgroup/TutorialGroupDeleteCommand.java new file mode 100644 index 00000000000..ee7fc9b0bac --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/tutorialgroup/TutorialGroupDeleteCommand.java @@ -0,0 +1,114 @@ +package seedu.address.logic.commands.tutorialgroup; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +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.logic.commands.student.StudentExpelCommand; +import seedu.address.logic.commands.student.StudentExpelCommand.EditStudentDescriptor; +import seedu.address.model.Model; +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.student.TutorialGroup; +import seedu.address.model.tag.Tag; + + +/** + * Deletes a tutorial group from the address book. + */ +public class TutorialGroupDeleteCommand extends Command { + public static final String COMMAND_WORD = "tutorial delete"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes a tutorial group from the address book. " + + "Parameters: index of the tutorial group (must be positive and NOT TOO BIG)"; + + public static final String MESSAGE_DELETE_TUTORIAL_GROUP_SUCCESS = "This tutorial group deleted: %1$s"; + + private Index targetIndex; + + /** + * Creates an TutorialGroupDeleteCommand to add the specified {@code Index} + */ + public TutorialGroupDeleteCommand(Index index) { + targetIndex = index; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredTutorialGroupList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TUTORIAL_GROUP_DISPLAYED_INDEX); + } + + TutorialGroup toDelete = lastShownList.get(targetIndex.getZeroBased()); + + List studentsInGroup = findStudentInGroup(model.getFilteredStudentList(), toDelete); + String res = expelStudentsFromGroup(model, studentsInGroup); + model.deleteTutorialGroup(toDelete); + String message = String.format(MESSAGE_DELETE_TUTORIAL_GROUP_SUCCESS, toDelete) + + "\n" + + "Students being expelled from this group \n" + + res; + + return new CommandResult(message); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TutorialGroupDeleteCommand // instanceof handles nulls + && targetIndex.equals(((TutorialGroupDeleteCommand) other).targetIndex)); + } + + private List findStudentInGroup(List students, TutorialGroup tutorialGroup) { + List selectedStudents = new ArrayList<>(); + + for (Student student : students) { + if (student.belongsTo(tutorialGroup)) { + selectedStudents.add(student); + } + } + return selectedStudents; + } + + private String expelStudentsFromGroup(Model model, List students) { + String res = ""; + for (Student student: students) { + StudentExpelCommand.EditStudentDescriptor editStudentDescriptor = + new StudentExpelCommand.EditStudentDescriptor(); + editStudentDescriptor.resetTutorialGroup(null); + Student editedStudent = createEditedStudent(student, editStudentDescriptor); + model.setStudent(student, editedStudent); + res += editedStudent + "\n"; + model.updateFilteredStudentList(PREDICATE_SHOW_ALL_PERSONS); + } + return res; + } + + /** + * Creates and returns a {@code Student} with the details of {@code studentToEdit} + * edited with {@code editStudentDescriptor}. + */ + private static Student createEditedStudent(Student studentToEdit, EditStudentDescriptor editStudentDescriptor) { + assert studentToEdit != null; + + Name updatedName = studentToEdit.getName(); + Phone updatedPhone = studentToEdit.getPhone(); + Email updatedEmail = studentToEdit.getEmail(); + Set updatedTags = studentToEdit.getTags(); + TutorialGroup updatedTutorialGroup = null; + return new Student(updatedName, updatedPhone, updatedEmail, updatedTags, updatedTutorialGroup); + } +} diff --git a/src/main/java/seedu/address/logic/commands/tutorialgroup/TutorialGroupFilterCommand.java b/src/main/java/seedu/address/logic/commands/tutorialgroup/TutorialGroupFilterCommand.java new file mode 100644 index 00000000000..3b1c881e67f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/tutorialgroup/TutorialGroupFilterCommand.java @@ -0,0 +1,63 @@ +package seedu.address.logic.commands.tutorialgroup; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +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.Student; +import seedu.address.model.student.TutorialGroup; + + + +/** + * Filters a tutorial group from the address book. + */ +public class TutorialGroupFilterCommand extends Command { + public static final String COMMAND_WORD = "tutorial filter"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Filters students based on tutorial groups. " + + "Parameters: tutorial group name"; + + public static final String MESSAGE_SUCCESS = "All students from the tutorial group %1$s:"; + public static final String MESSAGE_TUTORIAL_NOT_FOUND = "Tutorial group not found"; + private final TutorialGroup toFilter; + + /** + * Creates an TutorialGroupFilterCommand to add the specified {@code TutorialGroup} + */ + public TutorialGroupFilterCommand(TutorialGroup tutorialGroup) { + requireNonNull(tutorialGroup); + toFilter = tutorialGroup; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (!model.hasTutorialGroup(toFilter)) { + throw new CommandException(MESSAGE_TUTORIAL_NOT_FOUND); + } + + List students = model.getFilteredStudentList(); + model.updateFilteredStudentListByTg(toFilter); + String result = ""; + for (int i = 0; i < students.size(); i++) { + Student student = students.get(i); + if (student.belongsTo(toFilter)) { + result += student + "\n"; + } + } + return new CommandResult(String.format(MESSAGE_SUCCESS, toFilter) + "\n" + result); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TutorialGroupFilterCommand // instanceof handles nulls + && toFilter.equals(((TutorialGroupFilterCommand) other).toFilter)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/tutorialgroup/TutorialGroupListCommand.java b/src/main/java/seedu/address/logic/commands/tutorialgroup/TutorialGroupListCommand.java new file mode 100644 index 00000000000..4f6436346ae --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/tutorialgroup/TutorialGroupListCommand.java @@ -0,0 +1,38 @@ +package seedu.address.logic.commands.tutorialgroup; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TUTORIAL_GROUPS; + +import java.util.List; + +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.model.Model; +import seedu.address.model.student.TutorialGroup; + + + +/** + * Lists all tutorial groups in the address book to the user. + */ +public class TutorialGroupListCommand extends Command { + + public static final String COMMAND_WORD = "tutorial list"; + + public static final String MESSAGE_SUCCESS = "Listed all tutorial groups"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredTutorialGroupList(PREDICATE_SHOW_ALL_TUTORIAL_GROUPS); + List groups = model.getFilteredTutorialGroupList(); + String display = ""; + + for (int i = 0; i < groups.size(); i++) { + display += String.valueOf(i + 1) + ". " + groups.get(i).toString() + "\n"; + } + + return new CommandResult(display + MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java deleted file mode 100644 index 1e466792b46..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ /dev/null @@ -1,76 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses user input. - */ -public class AddressBookParser { - - /** - * Used for initial separation of command word and args. - */ - private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); - - /** - * Parses user input into command for execution. - * - * @param userInput full user input string - * @return the command based on the user input - * @throws ParseException if the user input does not conform the expected format - */ - public Command parseCommand(String userInput) throws ParseException { - final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); - if (!matcher.matches()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); - } - - final String commandWord = matcher.group("commandWord"); - final String arguments = matcher.group("arguments"); - switch (commandWord) { - - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); - - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); - - case DeleteCommand.COMMAND_WORD: - return new DeleteCommandParser().parse(arguments); - - case ClearCommand.COMMAND_WORD: - return new ClearCommand(); - - case FindCommand.COMMAND_WORD: - return new FindCommandParser().parse(arguments); - - case ListCommand.COMMAND_WORD: - return new ListCommand(); - - case ExitCommand.COMMAND_WORD: - return new ExitCommand(); - - case HelpCommand.COMMAND_WORD: - return new HelpCommand(); - - 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..e7cd3cd8782 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -11,5 +11,11 @@ public class CliSyntax { 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_TASK_NAME = new Prefix("tn/"); + public static final Prefix PREFIX_TASK_DESC = new Prefix("i/"); + public static final Prefix PREFIX_TASK_DEADLINE = new Prefix("d/"); + public static final Prefix PREFIX_TASK_STUDENT = new Prefix("s/"); + public static final Prefix PREFIX_TUTORIAL_GROUP = new Prefix("g/"); + public static final Prefix PREFIX_GRADE = new Prefix("gr/"); } diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java deleted file mode 100644 index 522b93081cc..00000000000 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ /dev/null @@ -1,29 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses input arguments and creates a new DeleteCommand object - */ -public class DeleteCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the DeleteCommand - * and returns a DeleteCommand object for execution. - * @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); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java deleted file mode 100644 index 4fb71f23103..00000000000 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ /dev/null @@ -1,33 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import java.util.Arrays; - -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Parses input arguments and creates a new FindCommand object - */ -public class FindCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the FindCommand - * and returns a FindCommand object for execution. - * @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)); - } - - String[] nameKeywords = trimmedArgs.split("\\s+"); - - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..27e90030891 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -9,11 +9,16 @@ 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.grade.Grade; +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.TutorialGroup; import seedu.address.model.tag.Tag; +import seedu.address.model.task.TaskDeadline; +import seedu.address.model.task.TaskDescription; +import seedu.address.model.task.TaskName; /** * Contains utility methods used for parsing strings in the various *Parser classes. @@ -35,6 +40,22 @@ public static Index parseIndex(String oneBasedIndex) throws ParseException { return Index.fromOneBased(Integer.parseInt(trimmedIndex)); } + /** + * Parses {@code Collection indexes} into a {@code Index[]}. Leading and trailing whitespaces will be + * trimmed. + * @throws ParseException if any of the specified indexes are invalid (not non-zero unsigned integer). + */ + public static Index[] parseIndexes(String arguments) throws ParseException { + String[] indexes = arguments.trim().split("\\s+"); + Index[] indexArray = new Index[indexes.length]; + for (int i = 0; i < indexes.length; i++) { + indexArray[i] = parseIndex(indexes[i]); + } + return indexArray; + } + + //Student Parser Util + /** * Parses a {@code String name} into a {@code Name}. * Leading and trailing whitespaces will be trimmed. @@ -95,6 +116,21 @@ public static Email parseEmail(String email) throws ParseException { return new Email(trimmedEmail); } + /** + * Parses a {@code String tutorialGroup} into an {@code TutorialGroup}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code tutorialGroup} is invalid. + */ + public static TutorialGroup parseTutorialGroup(String tutorialGroup) throws ParseException { + requireNonNull(tutorialGroup); + String trimmedGroup = tutorialGroup.trim(); + if (!TutorialGroup.isValidTutorialGroup(trimmedGroup)) { + throw new ParseException(TutorialGroup.MESSAGE_CONSTRAINTS); + } + return new TutorialGroup(trimmedGroup); + } + /** * Parses a {@code String tag} into a {@code Tag}. * Leading and trailing whitespaces will be trimmed. @@ -121,4 +157,68 @@ public static Set parseTags(Collection tags) throws ParseException } return tagSet; } + + //Task Parser Util + + /** + * Parses a {@code String taskName} into a {@code TaskName}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code taskName} is invalid. + */ + public static TaskName parseTaskName(String taskName) throws ParseException { + requireNonNull(taskName); + String trimmedTaskName = taskName.trim(); + if (!TaskName.isValidName(trimmedTaskName)) { + throw new ParseException(TaskName.MESSAGE_CONSTRAINTS); + } + return new TaskName(trimmedTaskName); + } + + /** + * Parses a {@code String taskDescription} into a {@code TaskDescription}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code taskDescription} is invalid. + */ + public static TaskDescription parseTaskDescription(String taskDescription) throws ParseException { + requireNonNull(taskDescription); + String trimmedTaskDescription = taskDescription.trim(); + if (!TaskDescription.isValidDescription(trimmedTaskDescription)) { + throw new ParseException(TaskDescription.MESSAGE_CONSTRAINTS); + } + return new TaskDescription(trimmedTaskDescription); + } + + /** + * Parses a {@code String taskDeadline} into a {@code taskDeadline}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code taskDeadline} is invalid. + */ + public static TaskDeadline parseTaskDeadline(String taskDeadline) throws ParseException { + requireNonNull(taskDeadline); + String trimmedTaskDeadline = taskDeadline.trim(); + if (!TaskDeadline.isInDeadlineFormat(trimmedTaskDeadline)) { + throw new ParseException(TaskDeadline.MESSAGE_CONSTRAINTS); + } else if (!TaskDeadline.isValidDate(trimmedTaskDeadline)) { + throw new ParseException(TaskDeadline.MESSAGE_INVALID_DATE); + } + return new TaskDeadline(trimmedTaskDeadline); + } + + /** + * Parses a {@code String grade} into a {@code Grade}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code Grade} is invalid. + */ + public static Grade parseGrade(String grade) throws ParseException { + requireNonNull(grade); + String trimmedGrade = grade.trim(); + if (!Grade.isValidDescription(trimmedGrade)) { + throw new ParseException(Grade.MESSAGE_CONSTRAINTS); + } + return (trimmedGrade.equals("T")) ? Grade.GRADED : Grade.UNGRADED; + } } diff --git a/src/main/java/seedu/address/logic/parser/TaaParser.java b/src/main/java/seedu/address/logic/parser/TaaParser.java new file mode 100644 index 00000000000..86caed90481 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/TaaParser.java @@ -0,0 +1,140 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.address.logic.commands.ClearCommand; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.HelpCommand; +import seedu.address.logic.commands.grade.GradeEditCommand; +import seedu.address.logic.commands.grade.GradeViewCommand; +import seedu.address.logic.commands.student.StudentAddCommand; +import seedu.address.logic.commands.student.StudentDeleteCommand; +import seedu.address.logic.commands.student.StudentEditCommand; +import seedu.address.logic.commands.student.StudentEnrollCommand; +import seedu.address.logic.commands.student.StudentExpelCommand; +import seedu.address.logic.commands.student.StudentListCommand; +import seedu.address.logic.commands.student.StudentResetFilterCommand; +import seedu.address.logic.commands.task.TaskAddCommand; +import seedu.address.logic.commands.task.TaskDeleteCommand; +import seedu.address.logic.commands.task.TaskEditCommand; +import seedu.address.logic.commands.task.TaskListCommand; +import seedu.address.logic.commands.tutorialgroup.TutorialGroupAddCommand; +import seedu.address.logic.commands.tutorialgroup.TutorialGroupDeleteCommand; +import seedu.address.logic.commands.tutorialgroup.TutorialGroupFilterCommand; +import seedu.address.logic.commands.tutorialgroup.TutorialGroupListCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.logic.parser.grade.GradeEditCommandParser; +import seedu.address.logic.parser.grade.GradeViewCommandParser; +import seedu.address.logic.parser.student.StudentAddCommandParser; +import seedu.address.logic.parser.student.StudentDeleteCommandParser; +import seedu.address.logic.parser.student.StudentEditCommandParser; +import seedu.address.logic.parser.student.StudentEnrollCommandParser; +import seedu.address.logic.parser.student.StudentExpelCommandParser; +import seedu.address.logic.parser.task.TaskAddCommandParser; +import seedu.address.logic.parser.task.TaskDeleteCommandParser; +import seedu.address.logic.parser.task.TaskEditCommandParser; +import seedu.address.logic.parser.tutorialgroup.TutorialGroupAddCommandParser; +import seedu.address.logic.parser.tutorialgroup.TutorialGroupDeleteCommandParser; + +/** + * Parses user input. + */ +public class TaaParser { + + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + private static final Pattern COMPLEX_COMMAND_FORMAT = Pattern.compile( + "(?(\\S+ \\S+))(?.*)" + ); + + + /** + * Parses user input into command for execution. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public Command parseCommand(String userInput) throws ParseException { + final Matcher matcher = COMPLEX_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + + final String commandWord = matcher.group("commandWord"); + final String arguments = matcher.group("arguments"); + switch (commandWord) { + + case StudentAddCommand.COMMAND_WORD: + return new StudentAddCommandParser().parse(arguments); + + case StudentDeleteCommand.COMMAND_WORD: + return new StudentDeleteCommandParser().parse(arguments); + + case StudentEnrollCommand.COMMAND_WORD: + return new StudentEnrollCommandParser().parse(arguments); + + case StudentExpelCommand.COMMAND_WORD: + return new StudentExpelCommandParser().parse(arguments); + + case ClearCommand.COMMAND_WORD: + return new ClearCommand(); + + case StudentListCommand.COMMAND_WORD: + return new StudentListCommand(); + + case StudentEditCommand.COMMAND_WORD: + return new StudentEditCommandParser().parse(arguments); + + case StudentResetFilterCommand.COMMAND_WORD: + return new StudentResetFilterCommand(); + + case TutorialGroupAddCommand.COMMAND_WORD: + return new TutorialGroupAddCommandParser().parse(arguments); + + case TutorialGroupDeleteCommand.COMMAND_WORD: + return new TutorialGroupDeleteCommandParser().parse(arguments); + + case TutorialGroupListCommand.COMMAND_WORD: + return new TutorialGroupListCommand(); + + case TutorialGroupFilterCommand.COMMAND_WORD: + return new TutorialGroupFilterCommandParser().parse(arguments); + + case ExitCommand.COMMAND_WORD: + return new ExitCommand(); + + case HelpCommand.COMMAND_WORD: + return new HelpCommand(); + + case TaskAddCommand.COMMAND_WORD: + return new TaskAddCommandParser().parse(arguments); + + case TaskEditCommand.COMMAND_WORD: + return new TaskEditCommandParser().parse(arguments); + + case TaskDeleteCommand.COMMAND_WORD: + return new TaskDeleteCommandParser().parse(arguments); + + case TaskListCommand.COMMAND_WORD: + return new TaskListCommand(); + + case GradeEditCommand.COMMAND_WORD: + return new GradeEditCommandParser().parse(arguments); + + case GradeViewCommand.COMMAND_WORD: + return new GradeViewCommandParser().parse(arguments); + + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/TutorialGroupFilterCommandParser.java b/src/main/java/seedu/address/logic/parser/TutorialGroupFilterCommandParser.java new file mode 100644 index 00000000000..f19411d7199 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/TutorialGroupFilterCommandParser.java @@ -0,0 +1,51 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TUTORIAL_GROUP; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.tutorialgroup.TutorialGroupFilterCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.student.TutorialGroup; + + + + + + +/** + * Parses input arguments and creates a new TutorialGroupFilterCommand object + */ +public class TutorialGroupFilterCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the TutorialGroupFilterCommand + * and returns an TutorialGroupFilterCommand object for execution. + * @return TutorialGroupFilterCommand + * @throws ParseException if the user input does not conform the expected format + */ + public TutorialGroupFilterCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TUTORIAL_GROUP); + + if (!arePrefixesPresent(argMultimap, PREFIX_TUTORIAL_GROUP) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + TutorialGroupFilterCommand.MESSAGE_USAGE)); + } + + TutorialGroup tutorialGroup = ParserUtil.parseTutorialGroup(argMultimap.getValue(PREFIX_TUTORIAL_GROUP).get()); + + return new TutorialGroupFilterCommand(tutorialGroup); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values 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/grade/GradeEditCommandParser.java b/src/main/java/seedu/address/logic/parser/grade/GradeEditCommandParser.java new file mode 100644 index 00000000000..0faaba04eee --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/grade/GradeEditCommandParser.java @@ -0,0 +1,63 @@ +package seedu.address.logic.parser.grade; + +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_GRADE; + +import java.util.logging.Level; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.grade.GradeEditCommand; +import seedu.address.logic.commands.grade.GradeEditCommand.EditGradeDescriptor; +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; + + + +/** + * Parses input arguments and creates a new GradeEditCommand object + */ +public class GradeEditCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditCommand + * and returns an EditCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public GradeEditCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer + .tokenize(args, PREFIX_GRADE); + + Index studentIndex; + Index taskIndex; + + try { + String[] before = argMultimap.getPreamble().split(" "); + studentIndex = ParserUtil.parseIndex(before[0]); + taskIndex = ParserUtil.parseIndex(before[1]); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + GradeEditCommand.MESSAGE_USAGE), pe); + } + + EditGradeDescriptor editGradeDescriptor = new EditGradeDescriptor(); + if (argMultimap.getValue(PREFIX_GRADE).isPresent()) { + editGradeDescriptor.setGrade(ParserUtil.parseGrade(argMultimap.getValue(PREFIX_GRADE).get())); + } else { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + GradeEditCommand.MESSAGE_USAGE)); + } + + LogsCenter.getLogger(GradeEditCommandParser.class).log(Level.INFO, "Prefix grade parsed as {0}", + argMultimap.getValue(PREFIX_GRADE)); + + return new GradeEditCommand(studentIndex, taskIndex, editGradeDescriptor); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/grade/GradeViewCommandParser.java b/src/main/java/seedu/address/logic/parser/grade/GradeViewCommandParser.java new file mode 100644 index 00000000000..fffcac3eea0 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/grade/GradeViewCommandParser.java @@ -0,0 +1,45 @@ +package seedu.address.logic.parser.grade; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.grade.GradeViewCommand; +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; + + +/** + * Parses input arguments and creates a new GradeViewCommand object + */ +public class GradeViewCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the GradeViewCommand + * and returns an GradeViewCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public GradeViewCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer + .tokenize(args); + + Index studentIndex; + Index taskIndex; + + try { + String[] before = argMultimap.getPreamble().split(" "); + studentIndex = ParserUtil.parseIndex(before[0]); + taskIndex = ParserUtil.parseIndex(before[1]); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + GradeViewCommand.MESSAGE_USAGE), pe); + } + return new GradeViewCommand(studentIndex, taskIndex); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/student/StudentAddCommandParser.java similarity index 50% rename from src/main/java/seedu/address/logic/parser/AddCommandParser.java rename to src/main/java/seedu/address/logic/parser/student/StudentAddCommandParser.java index 3b8bfa035e8..13eae842429 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/student/StudentAddCommandParser.java @@ -1,52 +1,63 @@ -package seedu.address.logic.parser; +package seedu.address.logic.parser.student; 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_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.CliSyntax.PREFIX_TUTORIAL_GROUP; import java.util.Set; import java.util.stream.Stream; -import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.student.StudentAddCommand; +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.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.Email; +import seedu.address.model.student.Name; +import seedu.address.model.student.Phone; +import seedu.address.model.student.Student; +import seedu.address.model.student.TutorialGroup; import seedu.address.model.tag.Tag; /** - * Parses input arguments and creates a new AddCommand object + * Parses input arguments and creates a new StudentAddCommand object */ -public class AddCommandParser implements Parser { - +public class StudentAddCommandParser implements Parser { /** - * Parses the given {@code String} of arguments in the context of the AddCommand - * and returns an AddCommand object for execution. + * Parses the given {@code String} of arguments in the context of the StudentAddCommand + * and returns an StudentAddCommand object for execution. * @throws ParseException if the user input does not conform the expected format */ - public AddCommand parse(String args) throws ParseException { + public StudentAddCommand 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_TAG, PREFIX_TUTORIAL_GROUP); - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL) || !argMultimap.getPreamble().isEmpty()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, StudentAddCommand.MESSAGE_USAGE)); } 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); + Student student; + if (arePrefixesPresent(argMultimap, PREFIX_TUTORIAL_GROUP)) { + TutorialGroup tutorialGroup = ParserUtil.parseTutorialGroup(argMultimap.getValue(PREFIX_TUTORIAL_GROUP) + .get()); + student = new Student(name, phone, email, tagList, tutorialGroup); + } else { + student = new Student(name, phone, email, tagList); + } - return new AddCommand(person); + return new StudentAddCommand(student); } /** diff --git a/src/main/java/seedu/address/logic/parser/student/StudentDeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/student/StudentDeleteCommandParser.java new file mode 100644 index 00000000000..df8e5bbbfd0 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/student/StudentDeleteCommandParser.java @@ -0,0 +1,29 @@ +package seedu.address.logic.parser.student; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.student.StudentDeleteCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new StudentDeleteCommand object + */ +public class StudentDeleteCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the StudentDeleteCommand + * and returns a StudentDeleteCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public StudentDeleteCommand parse(String args) throws ParseException { + try { + Index[] indexes = ParserUtil.parseIndexes(args); + return new StudentDeleteCommand(indexes); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, StudentDeleteCommand.MESSAGE_USAGE), pe); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/student/StudentEditCommandParser.java similarity index 51% rename from src/main/java/seedu/address/logic/parser/EditCommandParser.java rename to src/main/java/seedu/address/logic/parser/student/StudentEditCommandParser.java index 845644b7dea..bf52810afc5 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/student/StudentEditCommandParser.java @@ -1,12 +1,12 @@ -package seedu.address.logic.parser; +package seedu.address.logic.parser.student; 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_EMAIL; 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.CliSyntax.PREFIX_TUTORIAL_GROUP; import java.util.Collection; import java.util.Collections; @@ -14,54 +14,63 @@ import java.util.Set; 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.student.StudentEditCommand; +import seedu.address.logic.commands.student.StudentEditCommand.EditStudentDescriptor; +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.tag.Tag; /** - * Parses input arguments and creates a new EditCommand object + * Parses input arguments and creates a new StudentEditCommand object */ -public class EditCommandParser implements Parser { +public class StudentEditCommandParser implements Parser { /** - * Parses the given {@code String} of arguments in the context of the EditCommand - * and returns an EditCommand object for execution. + * Parses the given {@code String} of arguments in the context of the StudentEditCommand + * and returns an StudentEditCommand object for execution. * @throws ParseException if the user input does not conform the expected format */ - public EditCommand parse(String args) throws ParseException { + public StudentEditCommand 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_NAME, PREFIX_TUTORIAL_GROUP, PREFIX_PHONE, + PREFIX_EMAIL, 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); + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + StudentEditCommand.MESSAGE_USAGE), pe); } - EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); + EditStudentDescriptor editStudentDescriptor = new EditStudentDescriptor(); if (argMultimap.getValue(PREFIX_NAME).isPresent()) { - editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); + 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())); + if (argMultimap.getValue(PREFIX_TUTORIAL_GROUP).isPresent()) { + editStudentDescriptor.setTutorialGroup(ParserUtil.parseTutorialGroup(argMultimap + .getValue(PREFIX_TUTORIAL_GROUP).get())); } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); - if (!editPersonDescriptor.isAnyFieldEdited()) { - throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); + parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editStudentDescriptor::setTags); + + if (!editStudentDescriptor.isAnyFieldEdited()) { + throw new ParseException(StudentEditCommand.MESSAGE_NOT_EDITED); } - return new EditCommand(index, editPersonDescriptor); + return new StudentEditCommand(index, editStudentDescriptor); } /** diff --git a/src/main/java/seedu/address/logic/parser/student/StudentEnrollCommandParser.java b/src/main/java/seedu/address/logic/parser/student/StudentEnrollCommandParser.java new file mode 100644 index 00000000000..9988501d03a --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/student/StudentEnrollCommandParser.java @@ -0,0 +1,59 @@ +package seedu.address.logic.parser.student; + +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_TUTORIAL_GROUP; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.student.StudentEnrollCommand; +import seedu.address.logic.commands.student.StudentEnrollCommand.EditStudentDescriptor; +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; + + + + +/** + * Parses input arguments and creates a new StudentEnrollCommand object + */ +public class StudentEnrollCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the StudentEnrollCommand + * and returns an StudentEnrollCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public StudentEnrollCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer + .tokenize(args, PREFIX_TUTORIAL_GROUP); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + StudentEnrollCommand.MESSAGE_USAGE), pe); + } + + EditStudentDescriptor editStudentDescriptor = new EditStudentDescriptor(); + + if (argMultimap.getValue(PREFIX_TUTORIAL_GROUP).isPresent()) { + editStudentDescriptor.setTutorialGroup(ParserUtil.parseTutorialGroup(argMultimap + .getValue(PREFIX_TUTORIAL_GROUP).get())); + } + + + if (!editStudentDescriptor.isAnyFieldEdited()) { + throw new ParseException(StudentEnrollCommand.MESSAGE_NOT_EDITED); + } + + return new StudentEnrollCommand(index, editStudentDescriptor); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/student/StudentExpelCommandParser.java b/src/main/java/seedu/address/logic/parser/student/StudentExpelCommandParser.java new file mode 100644 index 00000000000..a053af07407 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/student/StudentExpelCommandParser.java @@ -0,0 +1,63 @@ +package seedu.address.logic.parser.student; + +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_TUTORIAL_GROUP; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.student.StudentExpelCommand; +import seedu.address.logic.commands.student.StudentExpelCommand.EditStudentDescriptor; +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.TutorialGroup; + + + +/** + * Parses input arguments and creates a new StudentExpelCommand object + */ +public class StudentExpelCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the StudentExpelCommand + * and returns an StudentExpelCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public StudentExpelCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer + .tokenize(args, PREFIX_TUTORIAL_GROUP); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + StudentExpelCommand.MESSAGE_USAGE), pe); + } + + EditStudentDescriptor editStudentDescriptor = new EditStudentDescriptor(); + + TutorialGroup tutorialGroup = null; + + if (argMultimap.getValue(PREFIX_TUTORIAL_GROUP).isPresent()) { + tutorialGroup = ParserUtil.parseTutorialGroup(argMultimap + .getValue(PREFIX_TUTORIAL_GROUP).get()); + + editStudentDescriptor.resetTutorialGroup(tutorialGroup); + } + + if (!editStudentDescriptor.isAnyFieldEdited()) { + throw new ParseException(StudentExpelCommand.MESSAGE_NOT_EDITED); + } + + + return new StudentExpelCommand(index, editStudentDescriptor); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/task/TaskAddCommandParser.java b/src/main/java/seedu/address/logic/parser/task/TaskAddCommandParser.java new file mode 100644 index 00000000000..5891fa0329d --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/task/TaskAddCommandParser.java @@ -0,0 +1,63 @@ +package seedu.address.logic.parser.task; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_DESC; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_STUDENT; + +import java.util.List; +import java.util.stream.Stream; + +import seedu.address.logic.commands.task.TaskAddCommand; +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.task.TaskDeadline; +import seedu.address.model.task.TaskDescription; +import seedu.address.model.task.TaskName; + +/** + * Parses input arguments and creates a new TaskAddCommand object + */ +public class TaskAddCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the TaskAddCommand + * and returns an TaskAddCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public TaskAddCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TASK_NAME, PREFIX_TASK_DESC, + PREFIX_TASK_DEADLINE, PREFIX_TASK_STUDENT); + + if (!arePrefixesPresent(argMultimap, PREFIX_TASK_NAME, PREFIX_TASK_DESC, + PREFIX_TASK_DEADLINE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, TaskAddCommand.MESSAGE_USAGE)); + } + + + TaskName taskName = ParserUtil.parseTaskName(argMultimap.getValue(PREFIX_TASK_NAME).get()); + TaskDescription taskDesc = ParserUtil.parseTaskDescription(argMultimap.getValue(PREFIX_TASK_DESC).get()); + TaskDeadline taskDeadline = ParserUtil.parseTaskDeadline(argMultimap.getValue(PREFIX_TASK_DEADLINE).get()); + + List studentNames = argMultimap.getAllValues(PREFIX_TASK_STUDENT); + + // Delay the creation of the task + return new TaskAddCommand(taskName, taskDesc, taskDeadline, studentNames); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values 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/task/TaskDeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/task/TaskDeleteCommandParser.java new file mode 100644 index 00000000000..8304b4e27ad --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/task/TaskDeleteCommandParser.java @@ -0,0 +1,31 @@ +package seedu.address.logic.parser.task; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.task.TaskDeleteCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new TaskDeleteCommand object + */ +public class TaskDeleteCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the TaskDeleteCommand + * and returns a TaskDeleteCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public TaskDeleteCommand parse(String args) throws ParseException { + try { + Index[] indexes = ParserUtil.parseIndexes(args); + return new TaskDeleteCommand(indexes); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, TaskDeleteCommand.MESSAGE_USAGE), pe); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/task/TaskEditCommandParser.java b/src/main/java/seedu/address/logic/parser/task/TaskEditCommandParser.java new file mode 100644 index 00000000000..4a5316481f8 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/task/TaskEditCommandParser.java @@ -0,0 +1,71 @@ +package seedu.address.logic.parser.task; + +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_TASK_DEADLINE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_DESC; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TASK_STUDENT; + +import java.util.List; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.task.TaskEditCommand; +import seedu.address.logic.commands.task.TaskEditCommand.EditTaskDescriptor; +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; + +/** + * Parses input arguments and creates a new TaskEditCommand object + */ +public class TaskEditCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the TaskEditCommand + * and returns an TaskEditCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public TaskEditCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TASK_NAME, PREFIX_TASK_DEADLINE, + PREFIX_TASK_DESC, PREFIX_TASK_STUDENT); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, TaskEditCommand.MESSAGE_USAGE), pe); + } + + EditTaskDescriptor editTaskDescriptor = new TaskEditCommand.EditTaskDescriptor(); + if (argMultimap.getValue(PREFIX_TASK_NAME).isPresent()) { + editTaskDescriptor.setTaskName(ParserUtil.parseTaskName(argMultimap.getValue(PREFIX_TASK_NAME).get())); + } + if (argMultimap.getValue(PREFIX_TASK_DEADLINE).isPresent()) { + editTaskDescriptor.setTaskDeadline( + ParserUtil.parseTaskDeadline(argMultimap.getValue(PREFIX_TASK_DEADLINE).get()) + ); + } + if (argMultimap.getValue(PREFIX_TASK_DESC).isPresent()) { + editTaskDescriptor.setTaskDescription( + ParserUtil.parseTaskDescription(argMultimap.getValue(PREFIX_TASK_DESC).get()) + ); + } + if (argMultimap.getValue(PREFIX_TASK_STUDENT).isPresent()) { + List names = argMultimap.getAllValues(PREFIX_TASK_STUDENT); + editTaskDescriptor.setStudentNames(names); + } + + if (!editTaskDescriptor.isAnyFieldEdited()) { + throw new ParseException(TaskEditCommand.MESSAGE_NOT_EDITED); + } + + return new TaskEditCommand(index, editTaskDescriptor); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/tutorialgroup/TutorialGroupAddCommandParser.java b/src/main/java/seedu/address/logic/parser/tutorialgroup/TutorialGroupAddCommandParser.java new file mode 100644 index 00000000000..c28aad97c7f --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/tutorialgroup/TutorialGroupAddCommandParser.java @@ -0,0 +1,50 @@ +package seedu.address.logic.parser.tutorialgroup; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TUTORIAL_GROUP; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.tutorialgroup.TutorialGroupAddCommand; +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.TutorialGroup; + +/** + * Parses input arguments and creates a new TutorialGroupAddCommand object + */ +public class TutorialGroupAddCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the TutorialGroupAddCommand + * and returns an TutorialGroupAddCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public TutorialGroupAddCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TUTORIAL_GROUP); + + if (!arePrefixesPresent(argMultimap, PREFIX_TUTORIAL_GROUP) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + TutorialGroupAddCommand.MESSAGE_USAGE)); + } + + TutorialGroup tutorialGroup = ParserUtil.parseTutorialGroup(argMultimap.getValue(PREFIX_TUTORIAL_GROUP).get()); + + return new TutorialGroupAddCommand(tutorialGroup); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values 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/tutorialgroup/TutorialGroupDeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/tutorialgroup/TutorialGroupDeleteCommandParser.java new file mode 100644 index 00000000000..4c786e17e4e --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/tutorialgroup/TutorialGroupDeleteCommandParser.java @@ -0,0 +1,31 @@ +package seedu.address.logic.parser.tutorialgroup; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.tutorialgroup.TutorialGroupDeleteCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new TutorialGroupDeleteCommand object + */ +public class TutorialGroupDeleteCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the TutorialGroupDeleteCommand + * and returns a TutorialGroupDeleteCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public TutorialGroupDeleteCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new TutorialGroupDeleteCommand(index); + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, TutorialGroupDeleteCommand.MESSAGE_USAGE), pe); + } + } + +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 1a943a0781a..432ba92fcb9 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -1,12 +1,26 @@ package seedu.address.model; import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TASKS; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; +import javafx.collections.ObservableMap; +import seedu.address.model.grade.Grade; +import seedu.address.model.grade.GradeKey; +import seedu.address.model.student.GradeMap; +import seedu.address.model.student.Student; +import seedu.address.model.student.TutorialGroup; +import seedu.address.model.student.UniqueStudentList; +import seedu.address.model.student.UniqueTutorialGroupList; +import seedu.address.model.task.Task; +import seedu.address.model.task.UniqueTaskList; /** * Wraps all data at the address-book level @@ -14,7 +28,10 @@ */ public class AddressBook implements ReadOnlyAddressBook { - private final UniquePersonList persons; + private final UniqueStudentList students; + private final UniqueTaskList tasks; + private final UniqueTutorialGroupList tutorialGroups; + private final GradeMap grades; /* * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication @@ -24,7 +41,10 @@ public class AddressBook implements ReadOnlyAddressBook { * among constructors. */ { - persons = new UniquePersonList(); + students = new UniqueStudentList(); + tutorialGroups = new UniqueTutorialGroupList(); + tasks = new UniqueTaskList(); + grades = new GradeMap(); } public AddressBook() {} @@ -40,81 +60,271 @@ public AddressBook(ReadOnlyAddressBook toBeCopied) { //// list overwrite operations /** - * Replaces the contents of the person list with {@code persons}. - * {@code persons} must not contain duplicate persons. + * Replaces the contents of the student list with {@code students}. + * {@code students} must not contain duplicate students. */ - public void setPersons(List persons) { - this.persons.setPersons(persons); + public void setStudents(List students) { + this.students.setStudents(students); } + /** + * Edit the student list in the task. + */ + public void editStudentInTask(Model model, Student oldStudent, Student newStudent) { + for (Task taskToEdit : tasks) { + if (taskToEdit.getStudents() != null) { + Set studentSet = taskToEdit.getStudents(); + Set newStudents = new HashSet<>(); + for (Student stud: studentSet) { + Student student; + if (Objects.equals(stud.getName(), oldStudent.getName())) { + student = newStudent; + } else { + student = stud; + } + newStudents.add(student); + } + Task editedTask = new Task(taskToEdit.getTaskName(), taskToEdit.getTaskDescription(), + taskToEdit.getTaskDeadline(), newStudents); + model.setTask(taskToEdit, editedTask); + model.updateGrades(taskToEdit, editedTask); + model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + } + } + } + + /** + * Delete the student list in the task. + */ + public void deleteStudentInTask(Model model, Student student) { + for (Task taskToEdit : tasks) { + if (taskToEdit.getStudents() != null) { + Set studentSet = taskToEdit.getStudents(); + Set newStudents = new HashSet<>(); + for (Student stud: studentSet) { + if (!Objects.equals(stud.getName(), student.getName())) { + newStudents.add(stud); + } + } + Task editedTask = new Task(taskToEdit.getTaskName(), taskToEdit.getTaskDescription(), + taskToEdit.getTaskDeadline(), newStudents); + model.setTask(taskToEdit, editedTask); + model.updateGrades(taskToEdit, editedTask); + model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + } + } + } + + /** + * Replaces the contents of the task list with {@code tasks}. + * {@code tasks} must not contain duplicate tasks. + */ + public void setTasks(List tasks) { + this.tasks.setTasks(tasks); + } + + /** + * Replaces the contents of the tutorial group list with {@code groups}. + * {@code groups} must not contain duplicate groups. + */ + public void setTutorialGroups(List groups) { + this.tutorialGroups.setTutorialGroups(groups); + } + + private void setGrades(ObservableMap gradeMap) { + this.grades.setGradeMapWithMap(gradeMap); + } /** * Resets the existing data of this {@code AddressBook} with {@code newData}. */ public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); - setPersons(newData.getPersonList()); + setStudents(newData.getStudentList()); + setTasks(newData.getTaskList()); + setTutorialGroups(newData.getTutorialGroupList()); + setGrades(newData.getGradeMap()); } //// person-level operations /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Returns true if a student with the same identity as {@code student} exists in the address book. + */ + public boolean hasStudent(Student student) { + requireNonNull(student); + return students.contains(student); + } + + /** + * Adds a student to the address book. + * The student must not already exist in the address book. + */ + public void addStudent(Student p) { + students.add(p); + } + + /** + * Replaces the given student {@code target} in the list with {@code editedStudent}. + * {@code target} must exist in the address book. + * The student identity of {@code editedStudent} must not be the same as another existing student in the + * address book. + */ + public void setStudent(Student target, Student editedStudent) { + requireNonNull(editedStudent); + + students.setStudent(target, editedStudent); + } + + /** + * Removes {@code key} from this {@code AddressBook}. + * {@code key} must exist in the address book. + */ + public void removeStudent(Student key) { + students.remove(key); + } + + /** + * Find the student based on name + * @return the student + */ + public Student findStudent(String name) { + return students.find(name); + } + + //// task methods + + /** + * Returns true if a task with the same identity as {@code task} exists in the address book. */ - public boolean hasPerson(Person person) { - requireNonNull(person); - return persons.contains(person); + public boolean hasTask(Task task) { + requireNonNull(task); + return tasks.contains(task); } /** - * Adds a person to the address book. - * The person must not already exist in the address book. + * Adds a task to the address book. + * The task must not already exist in the address book. */ - public void addPerson(Person p) { - persons.add(p); + public void addTask(Task t) { + tasks.add(t); } /** - * Replaces the given person {@code target} in the list with {@code editedPerson}. + * Replaces the given task {@code target} in the list with {@code editedTask}. * {@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. + * The task identity of {@code editedTask} must not be the same as another existing task in the address book. + */ + public void setTask(Task target, Task editedTask) { + requireNonNull(editedTask); + + tasks.setTask(target, editedTask); + } + + /** + * Removes {@code key} from this {@code AddressBook}. + * {@code key} must exist in the address book. + */ + public void removeTask(Task key) { + tasks.remove(key); + } + + // tutorial group level + /** + * Returns true if a tutorialGroup with the same identity as {@code tutorialGroup} exists in the address book. + */ + public boolean hasTutorialGroup(TutorialGroup tutorialGroup) { + requireNonNull(tutorialGroup); + return tutorialGroups.contains(tutorialGroup); + } + + /** + * Returns true if a tutorialGroup with the same identity as {@code tutorialGroup} exists in the address book. */ - public void setPerson(Person target, Person editedPerson) { - requireNonNull(editedPerson); + public boolean getTutorialGroup(TutorialGroup tutorialGroup) { + requireNonNull(tutorialGroup); + return tutorialGroups.contains(tutorialGroup); + } - persons.setPerson(target, editedPerson); + /** + * Adds a tutorialGroup to the address book. + * The tutorialGroup must not already exist in the address book. + */ + public void addTutorialGroup(TutorialGroup toAdd) { + tutorialGroups.add(toAdd); } + /** * 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 removeTutorialGroup(TutorialGroup key) { + tutorialGroups.remove(key); } //// util methods @Override public String toString() { - return persons.asUnmodifiableObservableList().size() + " persons"; + // return tasks.asUnmodifiableObservableList().size() + " tasks"; + return students.asUnmodifiableObservableList().size() + " persons"; // TODO: refine later } @Override - public ObservableList getPersonList() { - return persons.asUnmodifiableObservableList(); + public ObservableList getStudentList() { + return students.asUnmodifiableObservableList(); + } + + @Override + public ObservableList getTaskList() { + return tasks.asUnmodifiableObservableList(); + } + + @Override + public ObservableList getTutorialGroupList() { + return tutorialGroups.asUnmodifiableObservableList(); + } + + @Override + public ObservableMap getGradeMap() { + return grades.asUnmodifiableObservableMap(); } @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(); + } + + public void addGrade(GradeKey gradeKey, Grade grade) { + grades.add(gradeKey, grade); + } + + /** + * Updates the grade map by updating the tasks in the map. + * @param taskToEdit the current task associated with student(s) + * @param editedTask the task to be associated with taskToEdit's student(s) after the update + */ + public void updateGrades(Task taskToEdit, Task editedTask) { + Set currentGradeKeys = grades.asUnmodifiableObservableMap().keySet(); + Set badKeys = new HashSet<>(); + Map toAdd = new HashMap<>(); + for (GradeKey key : currentGradeKeys) { + if (key.task.isSameTask(taskToEdit)) { + Grade grade = grades.get(key); + toAdd.put(new GradeKey(key.student, editedTask), grade); + badKeys.add(key); + } + } + grades.removeAll(badKeys); + grades.addAll(toAdd); } } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..7d8d8d9591d 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -4,15 +4,27 @@ import java.util.function.Predicate; import javafx.collections.ObservableList; +import javafx.collections.ObservableMap; import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.Person; +import seedu.address.model.grade.Grade; +import seedu.address.model.grade.GradeKey; +import seedu.address.model.student.Student; +import seedu.address.model.student.TutorialGroup; +import seedu.address.model.task.Task; /** * The API of the Model component. */ public interface Model { /** {@code Predicate} that always evaluate to true */ - Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + + Predicate PREDICATE_SHOW_ALL_STUDENTS = unused -> true; + + Predicate PREDICATE_SHOW_ALL_TUTORIAL_GROUPS = unused -> true; + + /** {@code Predicate} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_TASKS = unused -> true; /** * Replaces user prefs data with the data in {@code userPrefs}. @@ -53,35 +65,115 @@ public interface Model { ReadOnlyAddressBook getAddressBook(); /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Returns true if a student with the same identity as {@code student} exists in the address book. */ - boolean hasPerson(Person person); + boolean hasStudent(Student student); /** - * Deletes the given person. - * The person must exist in the address book. + * Deletes the given student. + * The student must exist in the address book. */ - void deletePerson(Person target); + void deleteStudent(Student target); /** - * Adds the given person. - * {@code person} must not already exist in the address book. + * Find the student based on name + * @return the student */ - void addPerson(Person person); + Student findStudent(String name); /** - * Replaces the given person {@code target} with {@code editedPerson}. + * Adds the given student. + * {@code student} must not already exist in the address book. + */ + void addStudent(Student student); + + /** + * Replaces the given student {@code target} with {@code editedStudent}. * {@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. + * The student identity of {@code editedStudent} must not be the same as another existing student in the + * address book. + */ + void setStudent(Student target, Student editedStudent); + + /** Returns an unmodifiable view of the filtered student list */ + ObservableList getFilteredStudentList(); + + /** + * Updates the filter of the filtered student list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. */ - void setPerson(Person target, Person editedPerson); + void updateFilteredStudentList(Predicate predicate); + + /** + * Updates the filter of the filtered student list to filter by the given {@code tutorialGroup}. + * @throws NullPointerException if {@code tutorialGroup} is null. + */ + void updateFilteredStudentListByTg(TutorialGroup tutorialGroup); + + /** + * Returns true if a tutorial group with the same identity as {@code tutorialGroup} exists in the address book. + */ + boolean hasTutorialGroup(TutorialGroup tutorialGroup); + + /** + * Deletes the given tutorial group. + * The person must exist in the address book. + */ + void deleteTutorialGroup(TutorialGroup target); + + /** + * Adds the given tutorial group. + * {@code tutorialGroup} must not already exist in the address book. + */ + void addTutorialGroup(TutorialGroup tutorialGroup); /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); + ObservableList getFilteredTutorialGroupList(); + + + /** + * Updates the filter of the filtered tutorial group list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredTutorialGroupList(Predicate predicate); + + + /** + * Replaces the given task {@code target} with {@code editedTask}. + */ + boolean hasTask(Task target); + + /** + * Deletes the given task. + */ + void deleteTask(Task target); + + /** + * Adds the given task. + */ + void addTask(Task task); + + /** + * Replaces the given task {@code target} with {@code editedTask}. + */ + void setTask(Task target, Task editedTask); + + /** Returns an unmodifiable view of the filtered task list */ + ObservableList getFilteredTaskList(); /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * Updates the filter of the filtered task list to filter by the given {@code predicate}. * @throws NullPointerException if {@code predicate} is null. */ - void updateFilteredPersonList(Predicate predicate); + void updateFilteredTaskList(Predicate predicate); + + void addGrade(GradeKey gradeKey, Grade grade); + ObservableMap getGradeMap(); + + /** + * Updates the grade map by updating the tasks in the map. + * @param taskToEdit the current task associated with student(s) + * @param editedTask the task to be associated with taskToEdit's student(s) after the update + */ + void updateGrades(Task taskToEdit, Task editedTask); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 86c1df298d7..2108e1f2529 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -7,11 +7,17 @@ import java.util.function.Predicate; import java.util.logging.Logger; +import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import javafx.collections.ObservableMap; 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.grade.Grade; +import seedu.address.model.grade.GradeKey; +import seedu.address.model.student.Student; +import seedu.address.model.student.TutorialGroup; +import seedu.address.model.task.Task; /** * Represents the in-memory model of the address book data. @@ -21,7 +27,10 @@ public class ModelManager implements Model { private final AddressBook addressBook; private final UserPrefs userPrefs; - private final FilteredList filteredPersons; + private final FilteredList filteredStudents; + private final FilteredList filteredTasks; + private final FilteredList filteredTutorialGroups; + private final ObservableMap grades; /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -33,7 +42,11 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs this.addressBook = new AddressBook(addressBook); this.userPrefs = new UserPrefs(userPrefs); - filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + filteredStudents = new FilteredList<>(this.addressBook.getStudentList()); + filteredTasks = new FilteredList<>(this.addressBook.getTaskList()); + filteredTutorialGroups = new FilteredList<>(this.addressBook.getTutorialGroupList()); + grades = FXCollections.observableMap(this.addressBook.getGradeMap()); + Task.setGradesMap(grades); } public ModelManager() { @@ -88,44 +101,162 @@ public ReadOnlyAddressBook getAddressBook() { } @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return addressBook.hasPerson(person); + public boolean hasStudent(Student student) { + requireNonNull(student); + return addressBook.hasStudent(student); } @Override - public void deletePerson(Person target) { - addressBook.removePerson(target); + public void deleteStudent(Student target) { + addressBook.removeStudent(target); + addressBook.deleteStudentInTask(this, target); } @Override - public void addPerson(Person person) { - addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + public void addStudent(Student student) { + addressBook.addStudent(student); + updateFilteredStudentList(PREDICATE_SHOW_ALL_PERSONS); } @Override - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); + public Student findStudent(String studentName) { + return addressBook.findStudent(studentName); + } + + @Override + public void setStudent(Student target, Student editedStudent) { + requireAllNonNull(target, editedStudent); + + addressBook.setStudent(target, editedStudent); + addressBook.editStudentInTask(this, target, editedStudent); + } + + @Override + public boolean hasTutorialGroup(TutorialGroup tutorialGroup) { + requireNonNull(tutorialGroup); + return addressBook.hasTutorialGroup(tutorialGroup); + } - addressBook.setPerson(target, editedPerson); + @Override + public void deleteTutorialGroup(TutorialGroup target) { + addressBook.removeTutorialGroup(target); + } + + @Override + public void addTutorialGroup(TutorialGroup tutorialGroup) { + addressBook.addTutorialGroup(tutorialGroup); + updateFilteredTutorialGroupList(PREDICATE_SHOW_ALL_TUTORIAL_GROUPS); } - //=========== Filtered Person List Accessors ============================================================= + + //=========== Filtered Student List Accessors ============================================================= /** * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of * {@code versionedAddressBook} */ @Override - public ObservableList getFilteredPersonList() { - return filteredPersons; + public ObservableList getFilteredStudentList() { + return filteredStudents; + } + + @Override + public ObservableList getFilteredTutorialGroupList() { + return filteredTutorialGroups; + } + + @Override + public void updateFilteredStudentList(Predicate predicate) { + requireNonNull(predicate); + filteredStudents.setPredicate(predicate); + } + + @Override + public void updateFilteredStudentListByTg(TutorialGroup tutorialGroup) { + Predicate inSameTutorialGrp = s -> { + if (s.getTutorialGroup() != null) { + return s.getTutorialGroup().equals(tutorialGroup); + } else { + return false; + } + }; + requireNonNull(inSameTutorialGrp); + filteredStudents.setPredicate(inSameTutorialGrp); + } + + @Override + public void updateFilteredTutorialGroupList(Predicate predicate) { + requireNonNull(predicate); + filteredTutorialGroups.setPredicate(predicate); + } + + + + //=========== Task ================================================================================ + + @Override + public boolean hasTask(Task task) { + requireNonNull(task); + return addressBook.hasTask(task); + } + + @Override + public void deleteTask(Task target) { + addressBook.removeTask(target); + } + + @Override + public void addTask(Task task) { + addressBook.addTask(task); + updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + } + + @Override + public void setTask(Task target, Task editedTask) { + requireAllNonNull(target, editedTask); + + addressBook.setTask(target, editedTask); + } + + //=========== Filtered Task List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Task} backed by the internal list of + * {@code versionedAddressBook} + */ + @Override + public ObservableList getFilteredTaskList() { + return filteredTasks; } @Override - public void updateFilteredPersonList(Predicate predicate) { + public void updateFilteredTaskList(Predicate predicate) { requireNonNull(predicate); - filteredPersons.setPredicate(predicate); + filteredTasks.setPredicate(predicate); + } + + //=========== Grade Accessors ============================================================= + + /** + * Returns an unmodifiable view of the map of {@code (GradeKey, Grade)} backed by the internal list of + * {@code versionedAddressBook} + */ + @Override + public ObservableMap getGradeMap() { + return grades; + } + + //=========== Grade ================================================================================ + + @Override + public void addGrade(GradeKey gradeKey, Grade grade) { + addressBook.addGrade(gradeKey, grade); + //TODO: Show grade somehow + } + + @Override + public void updateGrades(Task taskToEdit, Task editedTask) { + addressBook.updateGrades(taskToEdit, editedTask); } @Override @@ -144,7 +275,7 @@ public boolean equals(Object obj) { ModelManager other = (ModelManager) obj; return addressBook.equals(other.addressBook) && userPrefs.equals(other.userPrefs) - && filteredPersons.equals(other.filteredPersons); + && filteredStudents.equals(other.filteredStudents); } } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 6ddc2cd9a29..1d86805bbd5 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -1,7 +1,12 @@ package seedu.address.model; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; +import javafx.collections.ObservableMap; +import seedu.address.model.grade.Grade; +import seedu.address.model.grade.GradeKey; +import seedu.address.model.student.Student; +import seedu.address.model.student.TutorialGroup; +import seedu.address.model.task.Task; /** * Unmodifiable view of an address book @@ -9,9 +14,26 @@ public interface ReadOnlyAddressBook { /** - * Returns an unmodifiable view of the persons list. + * Returns an unmodifiable view of the students list. * This list will not contain any duplicate persons. */ - ObservableList getPersonList(); + ObservableList getStudentList(); + + /** + * Returns an unmodifiable view of the students list. + * This list will not contain any duplicate students. + */ + ObservableList getTutorialGroupList(); + + /** + * Returns an unmodifiable view of the tasks list. + * This list will not contain any duplicate tasks. + */ + ObservableList getTaskList(); + + /** + * Returns an unmodifiable view of the grades map. + */ + ObservableMap getGradeMap(); } diff --git a/src/main/java/seedu/address/model/grade/Grade.java b/src/main/java/seedu/address/model/grade/Grade.java new file mode 100644 index 00000000000..41ecbc61527 --- /dev/null +++ b/src/main/java/seedu/address/model/grade/Grade.java @@ -0,0 +1,16 @@ +package seedu.address.model.grade; + +/** + * Represents a Grade (which is either Graded or Ungraded for now) + * Guarantees: details are present and not null, field values are validated and mutable. + */ +public enum Grade { + GRADED, + UNGRADED; + + public static final String MESSAGE_CONSTRAINTS = "Grade should either be T or F"; + + public static boolean isValidDescription(String trimmedGrade) { + return trimmedGrade.equals("T") || trimmedGrade.equals("F"); + } +} diff --git a/src/main/java/seedu/address/model/grade/GradeKey.java b/src/main/java/seedu/address/model/grade/GradeKey.java new file mode 100644 index 00000000000..2b11fb41e9c --- /dev/null +++ b/src/main/java/seedu/address/model/grade/GradeKey.java @@ -0,0 +1,55 @@ +package seedu.address.model.grade; + +import java.util.Objects; + +import seedu.address.model.student.Student; +import seedu.address.model.task.Task; + + +/** + * Represents the key to a particular Grade. + * Adapted from Stack Overflow:How to create a HashMap with two keys + */ +public class GradeKey { + public final Student student; + public final Task task; + + /** + * Constructor for the key to a grade. + * @param student the student whose grade needs to be retrieved + * @param task associated with the grade + */ + public GradeKey(final Student student, final Task task) { + this.student = student; + this.task = task; + } + + /** + * Checks if two keys to the Grade are equal. + * @param otherGradeKey the other Grade key + * @return true if the gradeKey and the task are equal, false otherwise + */ + @Override + public boolean equals(final Object otherGradeKey) { + if (!(otherGradeKey instanceof GradeKey)) { + return false; + } + if (!((GradeKey) otherGradeKey).student.equals(this.student)) { + return false; + } + return ((GradeKey) otherGradeKey).task.equals(this.task); + } + + @Override + public int hashCode() { + return Objects.hash(this.student, this.task); + } + + public Student getStudent() { + return this.student; + } + + public Task getTask() { + return this.task; + } +} 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/GradeMap.java b/src/main/java/seedu/address/model/student/GradeMap.java new file mode 100644 index 00000000000..4911503ee0b --- /dev/null +++ b/src/main/java/seedu/address/model/student/GradeMap.java @@ -0,0 +1,112 @@ +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.Map; +import java.util.Set; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableMap; +import seedu.address.model.grade.Grade; +import seedu.address.model.grade.GradeKey; +import seedu.address.model.student.exceptions.GradeKeyNotFoundException; + + +/** + * A map that maps a {@code GradeKey} to a {@code Grade} + */ +public class GradeMap implements Iterable { + + private final ObservableMap internalMap = FXCollections.observableHashMap(); + private final ObservableMap internalUnmodifiableMap = + FXCollections.unmodifiableObservableMap(internalMap); + + /** + * Returns true if the map contains an equivalent {@code GradeKey} as the given argument. + */ + public boolean contains(GradeKey gradeKey) { + requireNonNull(gradeKey); + return internalMap.containsKey(gradeKey); + } + + /** + * Adds or updates {@code (GradeKey, Grade)} entry to the map. + * The {@code GradeKey} must not already exist in the map. + */ + public void add(GradeKey gradeKey, Grade grade) { + requireAllNonNull(gradeKey, grade); + internalMap.put(gradeKey, grade); + } + + /** + * Gets the {@code Grade} associated with the given {@code GradeKey} + * @param gradeKey whose grade is required + * @return the required grade + */ + public Grade get(GradeKey gradeKey) { + return internalMap.get(gradeKey); + } + + /** + * Removes entry with the equivalent {@code GradeKey} key from the map. + * The GradeKey must exist in the list. + */ + public void remove(GradeKey toRemove) { + requireNonNull(toRemove); + if (!internalMap.containsKey(toRemove)) { + throw new GradeKeyNotFoundException(); + } + internalMap.remove(toRemove); + } + + /** + * Replaces the contents of this list with {@code grades}. + * {@code grades} will not contain duplicate . + */ + public void setGradeMapWithMap(Map grades) { + requireAllNonNull(grades); + internalMap.putAll(grades); + } + + /** + * Remove all entries from the map with the keys in the given key set. + * @param gradeKeySet the given key set + */ + public void removeAll(Set gradeKeySet) { + internalMap.keySet().removeAll(gradeKeySet); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableMap asUnmodifiableObservableMap() { + return internalUnmodifiableMap; + } + + @Override + public Iterator iterator() { + return internalMap.values().iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof GradeMap // instanceof handles nulls + && internalMap.equals(((GradeMap) other).internalMap)); + } + + @Override + public int hashCode() { + return internalMap.hashCode(); + } + + /** + * Adds all the entries from the given map to the internal map. + * @param toAdd entries to be added + */ + public void addAll(Map toAdd) { + internalMap.putAll(toAdd); + } +} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/student/Name.java similarity index 94% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/seedu/address/model/student/Name.java index 79244d71cf7..070a197199a 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 { diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/student/NameContainsKeywordsPredicate.java similarity index 77% rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java rename to src/main/java/seedu/address/model/student/NameContainsKeywordsPredicate.java index c9b5868427c..b036ed35273 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; @@ -6,9 +6,9 @@ import seedu.address.commons.util.StringUtil; /** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + * Tests that a {@code Student}'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,9 +16,9 @@ public NameContainsKeywordsPredicate(List keywords) { } @Override - public boolean test(Person person) { + public boolean test(Student student) { return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(student.getName().fullName, keyword)); } @Override diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/student/Phone.java similarity index 93% rename from src/main/java/seedu/address/model/person/Phone.java rename to src/main/java/seedu/address/model/student/Phone.java index 872c76b382f..c8573a772b2 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/address/model/student/Phone.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 phone number in the address book. + * Represents a Student's phone number in the address book. * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)} */ public class Phone { diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/student/Student.java similarity index 53% rename from src/main/java/seedu/address/model/person/Person.java rename to src/main/java/seedu/address/model/student/Student.java index 8ff1d83fe89..049adc4392f 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/student/Student.java @@ -1,4 +1,4 @@ -package seedu.address.model.person; +package seedu.address.model.student; import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; @@ -10,30 +10,45 @@ import seedu.address.model.tag.Tag; /** - * Represents a Person in the address book. + * Represents a Student in the address book. * Guarantees: details are present and not null, field values are validated, immutable. */ -public class Person { +public class Student { // Identity fields private final Name name; private final Phone phone; private final Email email; + private final TutorialGroup tutorialGroup; // 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); + public Student(Name name, Phone phone, Email email, Set tags) { + requireAllNonNull(name, phone, email, tags); this.name = name; this.phone = phone; this.email = email; - this.address = address; this.tags.addAll(tags); + this.tutorialGroup = null; + } + + /** + * Overload the constructor. + */ + public Student(Name name, Phone phone, Email email, Set tags, TutorialGroup tutorialGroup) { + requireAllNonNull(name, phone, email, tags); + this.name = name; + this.phone = phone; + this.email = email; + this.tags.addAll(tags); + this.tutorialGroup = tutorialGroup; + if (tutorialGroup != null) { + tutorialGroup.addStudentToTutorialGroup(this); + } } public Name getName() { @@ -48,10 +63,16 @@ public Email getEmail() { return email; } - public Address getAddress() { - return address; + + public boolean isEnrolledInTutorial() { + return tutorialGroup != null; + } + + public TutorialGroup getTutorialGroup() { + return tutorialGroup; } + /** * Returns an immutable tag set, which throws {@code UnsupportedOperationException} * if modification is attempted. @@ -64,13 +85,13 @@ public Set getTags() { * 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) { + public boolean isSameStudent(Student otherStudent) { + if (otherStudent == this) { return true; } - return otherPerson != null - && otherPerson.getName().equals(getName()); + return otherStudent != null + && otherStudent.getName().equals(getName()); } /** @@ -83,22 +104,25 @@ public boolean equals(Object other) { return true; } - if (!(other instanceof Person)) { + if (!(other instanceof Student)) { 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()); + Student otherStudent = (Student) other; + return otherStudent.getName().equals(getName()) + && otherStudent.getPhone().equals(getPhone()) + && otherStudent.getEmail().equals(getEmail()) + && otherStudent.getTags().equals(getTags()); + } + + public boolean belongsTo(TutorialGroup group) { + return group.equals(this.tutorialGroup); } @Override public int hashCode() { // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); + return Objects.hash(name, phone, email, tags); } @Override @@ -108,11 +132,14 @@ public String toString() { .append("; Phone: ") .append(getPhone()) .append("; Email: ") - .append(getEmail()) - .append("; Address: ") - .append(getAddress()); + .append(getEmail()); Set tags = getTags(); + TutorialGroup tutorialGroup = getTutorialGroup(); + if (tutorialGroup != null) { + builder.append("; Tutorial Group: ") + .append(getTutorialGroup()); + } if (!tags.isEmpty()) { builder.append("; Tags: "); tags.forEach(builder::append); diff --git a/src/main/java/seedu/address/model/student/TutorialGroup.java b/src/main/java/seedu/address/model/student/TutorialGroup.java new file mode 100644 index 00000000000..b0c0eb76ed6 --- /dev/null +++ b/src/main/java/seedu/address/model/student/TutorialGroup.java @@ -0,0 +1,87 @@ +package seedu.address.model.student; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.util.ArrayList; + +/** + * Represents a Student's tutorial group in the address book. + */ +public class TutorialGroup { + public static final String MESSAGE_CONSTRAINTS = + "Tutorial group should follow the format Txx, where x is a numeric value, and it should not be blank"; + + + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[T][0-9]{2}"; + + public static final String DEFAULT_TUTORIAL_GROUP = ""; + + private final String tutorialGroup; + + private final ArrayList students = new ArrayList<>(); + + + /** + * Constructs a {@code TutorialGroup}. + * + * @param group A valid tutorial group. + */ + public TutorialGroup(String group) { + requireNonNull(group); + checkArgument(isValidTutorialGroup(group), MESSAGE_CONSTRAINTS); + tutorialGroup = group; + } + + + /** + * Returns true if a given string is a valid tutorial group. + */ + public static boolean isValidTutorialGroup(String test) { + return test.matches(VALIDATION_REGEX); + } + + public void addStudentToTutorialGroup(Student student) { + students.add(student); + } + + public ArrayList getStudents() { + return students; + } + + /** + * Returns true if a given tutorial group is the same. + * @param group the given tutorial group. + * @return true if the two tutorial groups are the same, false otherwise. + */ + public boolean isSameTutorialGroup(TutorialGroup group) { + if (this.tutorialGroup == null && group.tutorialGroup == null) { + return true; + } else if (this.tutorialGroup == null) { + return false; + } + return this.tutorialGroup.equals(group.tutorialGroup); + } + + @Override + public String toString() { + return tutorialGroup; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TutorialGroup // instanceof handles nulls + && this.isSameTutorialGroup(((TutorialGroup) other))); // state check + } + + @Override + public int hashCode() { + return tutorialGroup.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/student/TutorialNameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/student/TutorialNameContainsKeywordsPredicate.java new file mode 100644 index 00000000000..e95b40fee9b --- /dev/null +++ b/src/main/java/seedu/address/model/student/TutorialNameContainsKeywordsPredicate.java @@ -0,0 +1,33 @@ +package seedu.address.model.student; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + + + +/** + * Tests that a {@code Student}'s {@code tutorialGroup} {@code name} matches any of the keywords given. + */ +public class TutorialNameContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public TutorialNameContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(TutorialGroup tutorialGroup) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(tutorialGroup.toString(), keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TutorialNameContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((TutorialNameContainsKeywordsPredicate) other).keywords)); // state check + } + +} 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..89f44f582c0 --- /dev/null +++ b/src/main/java/seedu/address/model/student/UniqueStudentList.java @@ -0,0 +1,145 @@ +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 students that enforces uniqueness between its elements and does not allow nulls. + * A student is considered unique by comparing using {@code Student#isSameStudent(Student)}. As such, adding and + * updating of persons uses Student#isSameStudent(Student) 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 + * Student#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 student as the given argument. + */ + public boolean contains(Student toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameStudent); + } + + /** + * Finds a student in the list with the same name as the given argument. + */ + public Student find(String toFind) { + requireNonNull(toFind); + return internalList.stream().filter(s -> toFind.equals(s.getName().toString())).findFirst().orElse(null); + } + + /** + * Adds a student to the list. + * The student 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 student {@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 student in the list. + */ + public void setStudent(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 student from the list. + * The student 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/UniqueTutorialGroupList.java b/src/main/java/seedu/address/model/student/UniqueTutorialGroupList.java new file mode 100644 index 00000000000..38754aab197 --- /dev/null +++ b/src/main/java/seedu/address/model/student/UniqueTutorialGroupList.java @@ -0,0 +1,112 @@ +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.DuplicateTutorialGroupException; +import seedu.address.model.student.exceptions.TutorialGroupNotFoundException; + + +/** + * A list of tutorial groups that enforces uniqueness between its elements and does not allow nulls. + * A tutorial group is considered unique by comparing using {@code TutorialGroup#isSameTutorialGroup(group)}. + */ +public class UniqueTutorialGroupList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent tutorial group as the given argument. + */ + public boolean contains(TutorialGroup toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::equals); + } + + /** + * Adds a tutorial group to the list. + * The tutorial group must not already exist in the list. + */ + public void add(TutorialGroup toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateTutorialGroupException(); + } + internalList.add(toAdd); + } + + + /** + * Removes the equivalent tutorial group from the list. + * The tutorial group must exist in the list. + */ + public void remove(TutorialGroup toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new TutorialGroupNotFoundException(); + } + } + + public void setStudents(UniqueTutorialGroupList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code tutorial group}. + * {@code tasks} must not contain duplicate tutorial groups. + */ + public void setTutorialGroups(List groups) { + requireAllNonNull(groups); + if (!tutorialGroupsAreUnique(groups)) { + throw new DuplicateTutorialGroupException(); + } + + internalList.setAll(groups); + } + + /** + * 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 UniqueTutorialGroupList // instanceof handles nulls + && internalList.equals(((UniqueTutorialGroupList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code tutorial group} contains only unique tutorial group. + */ + private boolean tutorialGroupsAreUnique(List tasks) { + for (int i = 0; i < tasks.size() - 1; i++) { + for (int j = i + 1; j < tasks.size(); j++) { + if (tasks.get(i).equals(tasks.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/student/exceptions/DuplicateGradeKeyException.java b/src/main/java/seedu/address/model/student/exceptions/DuplicateGradeKeyException.java new file mode 100644 index 00000000000..a11b74a3830 --- /dev/null +++ b/src/main/java/seedu/address/model/student/exceptions/DuplicateGradeKeyException.java @@ -0,0 +1,10 @@ +package seedu.address.model.student.exceptions; + +/** + * Signals that the operation cannot proceed because the {@code GradeKey} is already present in the map + */ +public class DuplicateGradeKeyException extends RuntimeException { + public DuplicateGradeKeyException() { + super("A student-task association can not have multiple grade values!"); + } +} 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..3de7ef8a78b --- /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 persons"); + } +} diff --git a/src/main/java/seedu/address/model/student/exceptions/DuplicateTutorialGroupException.java b/src/main/java/seedu/address/model/student/exceptions/DuplicateTutorialGroupException.java new file mode 100644 index 00000000000..a1cb1214bad --- /dev/null +++ b/src/main/java/seedu/address/model/student/exceptions/DuplicateTutorialGroupException.java @@ -0,0 +1,11 @@ +package seedu.address.model.student.exceptions; + +/** + * Signals that the operation will result in duplicate TutorialGroups (TutorialGroups are considered duplicates if they + * have the same identity). + */ +public class DuplicateTutorialGroupException extends RuntimeException { + public DuplicateTutorialGroupException() { + super("Operation would result in duplicate tutorial groups"); + } +} diff --git a/src/main/java/seedu/address/model/student/exceptions/GradeKeyNotFoundException.java b/src/main/java/seedu/address/model/student/exceptions/GradeKeyNotFoundException.java new file mode 100644 index 00000000000..5119de7bef5 --- /dev/null +++ b/src/main/java/seedu/address/model/student/exceptions/GradeKeyNotFoundException.java @@ -0,0 +1,10 @@ +package seedu.address.model.student.exceptions; + +/** + * Signals that the operation cannot proceed because the {@code GradeKey} is not present in the map + */ +public class GradeKeyNotFoundException extends RuntimeException { + public GradeKeyNotFoundException() { + super("The specified student-task pair is not found"); + } +} 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/student/exceptions/TutorialGroupNotFoundException.java b/src/main/java/seedu/address/model/student/exceptions/TutorialGroupNotFoundException.java new file mode 100644 index 00000000000..c7a39e20786 --- /dev/null +++ b/src/main/java/seedu/address/model/student/exceptions/TutorialGroupNotFoundException.java @@ -0,0 +1,11 @@ +package seedu.address.model.student.exceptions; + +/** + * Signals that the operation will result in TutorialGroup not found (TutorialGroup are considered not found if they + * are null). + */ +public class TutorialGroupNotFoundException extends RuntimeException { + public TutorialGroupNotFoundException() { + super("The specified tutorial group is not found."); + } +} diff --git a/src/main/java/seedu/address/model/task/Task.java b/src/main/java/seedu/address/model/task/Task.java new file mode 100644 index 00000000000..b66d5fcadd7 --- /dev/null +++ b/src/main/java/seedu/address/model/task/Task.java @@ -0,0 +1,149 @@ +package seedu.address.model.task; + +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 javafx.collections.ObservableMap; +import seedu.address.model.grade.Grade; +import seedu.address.model.grade.GradeKey; +import seedu.address.model.student.Student; + +/** + * Represents a Task in the address book. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Task { + + // A static variable is used because the same gradesMap is used to track + // students' grades for tasks across all tasks. + private static ObservableMap gradesMap; + + // Identity fields + private final TaskName taskName; + + // Data fields + private final TaskDescription taskDescription; + private final TaskDeadline taskDeadline; + private final Set students = new HashSet<>(); + + /** + * Every field must be present and not null. + */ + public Task(TaskName taskName, TaskDescription taskDescription, TaskDeadline taskDeadline, Set students) { + requireAllNonNull(taskName, taskDescription, taskDeadline, students); + this.taskName = taskName; + this.taskDescription = taskDescription; + this.taskDeadline = taskDeadline; + this.students.addAll(students); + } + + public TaskName getTaskName() { + return taskName; + } + + public TaskDescription getTaskDescription() { + return taskDescription; + } + + public TaskDeadline getTaskDeadline() { + return taskDeadline; + } + + /** + * Returns an immutable Student set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getStudents() { + return Collections.unmodifiableSet(students); + } + + /** + * Returns an immutable Grade set, which throws {@code UnsupportedOperationException} + */ + public static ObservableMap getGradesMap() { + return gradesMap; + } + + /** + * Sets the gradesMap to the given ObservableMap. + */ + public static void setGradesMap(ObservableMap gradesMap) { + Task.gradesMap = gradesMap; + } + + /** + * Returns true if the task is assigned to the specified student. + * + * @param student given student + * @return true if the task is assigned to the given student, false otherwise + */ + public boolean hasStudent(Student student) { + return students.contains(student); + } + + /** + * Returns true if both tasks of the same name have at least one other identity field that is the same. + * This defines a weaker notion of equality between two tasks. + */ + public boolean isSameTask(Task otherTask) { + if (otherTask == this) { + return true; + } + + return otherTask != null + && otherTask.getTaskName().equals(getTaskName()); + } + + /** + * Returns true if both tasks have the same identity and data fields. + * This defines a stronger notion of equality between two tasks. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Task)) { + return false; + } + + Task otherTask = (Task) other; + return otherTask.getTaskName().equals(getTaskName()) + && otherTask.getTaskDescription().equals(getTaskDescription()) + && otherTask.getTaskDeadline().equals(getTaskDeadline()) + && otherTask.getStudents().equals(getStudents()); + } + + @Override + public int hashCode() { + return Objects.hash(taskName, taskDescription, taskDeadline, students); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getTaskName()) + .append("\n") + .append("Task Description: \n") + .append(getTaskDescription()) + .append("\n") + .append("Task Deadline: \n") + .append(getTaskDeadline().toString()) + .append("\n") + .append("Students: \n"); + + Set students = getStudents(); + if (students.isEmpty()) { + builder.append("None"); + } else { + students.forEach(s -> builder.append(s + "\n")); + } + return builder.toString(); + } + +} diff --git a/src/main/java/seedu/address/model/task/TaskDeadline.java b/src/main/java/seedu/address/model/task/TaskDeadline.java new file mode 100644 index 00000000000..602976ec217 --- /dev/null +++ b/src/main/java/seedu/address/model/task/TaskDeadline.java @@ -0,0 +1,84 @@ +package seedu.address.model.task; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Represents a Task's deadline in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidDate(String)} + */ +public class TaskDeadline { + + public static final String MESSAGE_CONSTRAINTS = + "Deadline should be in the format of DD/MM/YYYY"; + public static final String MESSAGE_INVALID_DATE = + "Either the day or month or both is out of range"; + public static final String VALIDATION_REGEX = "[0-9]{2}/[0-9]{2}/[0-9]{4}"; + private static final String DATE_FORMAT = "dd/MM/yyyy"; + public final Date deadline; + + /** + * Constructs a {@code Deadline}. + * + * @param deadline A valid deadline. + */ + public TaskDeadline(String deadline) { + Date dateDeadline; + requireNonNull(deadline); + checkArgument(isInDeadlineFormat(deadline), MESSAGE_CONSTRAINTS); + checkArgument(isValidDate(deadline), MESSAGE_INVALID_DATE); + try { + dateDeadline = new SimpleDateFormat(DATE_FORMAT).parse(deadline); + } catch (ParseException e) { + e.printStackTrace(); + dateDeadline = null; + } + this.deadline = dateDeadline; + } + + /** + * Returns true if a given string is in dd/MM/yyyy. + */ + public static boolean isInDeadlineFormat(String test) { + return test.matches(VALIDATION_REGEX); + } + + /** + * Returns true if a given string is a valid date. + */ + public static boolean isValidDate(String test) { + DateFormat sdf = new SimpleDateFormat(DATE_FORMAT); + sdf.setLenient(false); + try { + sdf.parse(test); + return true; + } catch (ParseException e) { + e.printStackTrace(); + return false; + } + } + + @Override + public String toString() { + DateFormat formatter = new SimpleDateFormat(DATE_FORMAT); + return formatter.format(deadline); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TaskDeadline // instanceof handles nulls + && deadline.equals(((TaskDeadline) other).deadline)); // state check + } + + @Override + public int hashCode() { + return deadline.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/task/TaskDescription.java b/src/main/java/seedu/address/model/task/TaskDescription.java new file mode 100644 index 00000000000..5e056eb3ab9 --- /dev/null +++ b/src/main/java/seedu/address/model/task/TaskDescription.java @@ -0,0 +1,58 @@ +package seedu.address.model.task; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Task's description in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidDescription(String)} + */ +public class TaskDescription { + + public static final String MESSAGE_CONSTRAINTS = + "Description should not be blank"; + + /** + * The first character of the description must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[^\\s].*"; + + public final String description; + + /** + * Constructs a {@code TaskDescription}. + * + * @param description A valid description. + */ + public TaskDescription(String description) { + requireNonNull(description); + checkArgument(isValidDescription(description), MESSAGE_CONSTRAINTS); + this.description = description; + } + + /** + * Returns true if a given string is a valid description. + */ + public static boolean isValidDescription(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return description; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TaskDescription // instanceof handles nulls + && description.equals(((TaskDescription) other).description)); // state check + } + + @Override + public int hashCode() { + return description.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/task/TaskName.java b/src/main/java/seedu/address/model/task/TaskName.java new file mode 100644 index 00000000000..c1406c3cb76 --- /dev/null +++ b/src/main/java/seedu/address/model/task/TaskName.java @@ -0,0 +1,57 @@ +package seedu.address.model.task; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Task Name in the address book. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class TaskName { + + public static final String MESSAGE_CONSTRAINTS = + "Task names should only contain alphanumeric characters and spaces, and it should not be blank"; + + /* + * The first character of the address must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + public final String taskName; + + /** + * Constructs a {@code Name}. + * + * @param name A valid task name. + */ + public TaskName(String name) { + requireNonNull(name); + checkArgument(isValidName(name), MESSAGE_CONSTRAINTS); + taskName = name; + } + + /** + * Returns true if a given string is a valid name. + */ + public static boolean isValidName(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return taskName; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TaskName // instanceof handles nulls + && taskName.equals(((TaskName) other).taskName)); // state check + } + + @Override + public int hashCode() { + return taskName.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/task/TaskNameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/task/TaskNameContainsKeywordsPredicate.java new file mode 100644 index 00000000000..2fb31b9efd7 --- /dev/null +++ b/src/main/java/seedu/address/model/task/TaskNameContainsKeywordsPredicate.java @@ -0,0 +1,32 @@ +package seedu.address.model.task; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + + +/** + * Tests that a {@code Task}'s {@code TaskName} matches any of the keywords given. + */ +public class TaskNameContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public TaskNameContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Task task) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(task.toString(), keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TaskNameContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((TaskNameContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/task/UniqueTaskList.java b/src/main/java/seedu/address/model/task/UniqueTaskList.java new file mode 100644 index 00000000000..a1167c400b8 --- /dev/null +++ b/src/main/java/seedu/address/model/task/UniqueTaskList.java @@ -0,0 +1,149 @@ +package seedu.address.model.task; + +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.task.exceptions.DuplicateTaskException; +import seedu.address.model.task.exceptions.TaskNotFoundException; + +/** + * A list of tasks that enforces uniqueness between its elements and does not allow nulls. + * A task is considered unique by comparing using {@code Task#isSameTask(Task)}. As such, adding and updating of + * task uses Task#isSameTask(Task) for equality so as to ensure that the task being added or updated is + * unique in terms of identity in the UniqueTaskList. However, the removal of a task uses Task#equals(Object) so + * as to ensure that the task with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Task#isSameTask(Task) + */ +public class UniqueTaskList 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(Task toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameTask); + } + + /** + * Adds a task to the list. + * The task must not already exist in the list. + */ + public void add(Task toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateTaskException(); + } + internalList.add(toAdd); + sortByDeadline(); + } + + /** + * Replaces the task {@code target} in the list with {@code editedTask}. + * {@code target} must exist in the list. + * The task identity of {@code editedTask} must not be the same as another existing task in the list. + */ + public void setTask(Task target, Task editedTask) { + requireAllNonNull(target, editedTask); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new TaskNotFoundException(); + } + + if (!target.isSameTask(editedTask) && contains(editedTask)) { + throw new DuplicateTaskException(); + } + + internalList.set(index, editedTask); + sortByDeadline(); + } + + /** + * Removes the equivalent task from the list. + * The task must exist in the list. + */ + public void remove(Task toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new TaskNotFoundException(); + } + } + + public void setTasks(UniqueTaskList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + sortByDeadline(); + } + + /** + * Replaces the contents of this list with {@code tasks}. + * {@code tasks} must not contain duplicate tasks. + */ + public void setTasks(List tasks) { + requireAllNonNull(tasks); + if (!tasksAreUnique(tasks)) { + throw new DuplicateTaskException(); + } + internalList.setAll(tasks); + sortByDeadline(); + } + + /** + * Sorts the tasks by its deadline. + */ + public void sortByDeadline() { + internalList.sort(Comparator.comparing(task -> task.getTaskDeadline().deadline)); + //FXCollections.sort(internalList, taskComparator); + } + + /** + * 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 UniqueTaskList // instanceof handles nulls + && internalList.equals(((UniqueTaskList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code tasks} contains only unique tasks. + */ + private boolean tasksAreUnique(List tasks) { + for (int i = 0; i < tasks.size() - 1; i++) { + for (int j = i + 1; j < tasks.size(); j++) { + if (tasks.get(i).isSameTask(tasks.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java b/src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java new file mode 100644 index 00000000000..09bf8955cf9 --- /dev/null +++ b/src/main/java/seedu/address/model/task/exceptions/DuplicateTaskException.java @@ -0,0 +1,11 @@ +package seedu.address.model.task.exceptions; + +/** + * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same + * identity). + */ +public class DuplicateTaskException extends RuntimeException { + public DuplicateTaskException() { + super("Operation would result in duplicate tasks"); + } +} diff --git a/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java b/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java new file mode 100644 index 00000000000..f5dcaa1849e --- /dev/null +++ b/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.task.exceptions; + +/** + * Signals that the operation is unable to find the specified person. + */ +public class TaskNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..b4b868f9ed8 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -6,44 +6,37 @@ 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.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"), - new Address("Blk 30 Geylang Street 29, #06-40"), + public static Student[] getSampleStudents() { + return new Student[] { + new Student(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), + new Student(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), + new Student(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), + new Student(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), + new Student(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), + new Student(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), getTagSet("colleagues")) }; } public static ReadOnlyAddressBook getSampleAddressBook() { AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); + for (Student sampleStudent : getSampleStudents()) { + sampleAb.addStudent(sampleStudent); } return sampleAb; } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedGrade.java b/src/main/java/seedu/address/storage/JsonAdaptedGrade.java new file mode 100644 index 00000000000..467b633dd7e --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedGrade.java @@ -0,0 +1,43 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.grade.Grade; + +/** + * Jackson-friendly version of {@link Grade}. + */ +class JsonAdaptedGrade { + + public static final String MISSING_STUDENT_FIELD_MESSAGE_FORMAT = "Student's %s field is missing!"; + public static final String MISSING_TASK_FIELD_MESSAGE_FORMAT = "Task's %s field is missing!"; + + private final boolean grade; + + /** + * Constructs a {@code JsonAdaptedTask} with the given grade details. + */ + @JsonCreator + public JsonAdaptedGrade(@JsonProperty("grade") boolean grade) { + this.grade = grade; + } + + /** + * Converts a given {@code Grade} into this class for Jackson use. + */ + public JsonAdaptedGrade(Grade source) { + this.grade = source == Grade.GRADED; + } + + /** + * Converts this Jackson-friendly adapted grade object into the model's {@code Task} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted grade. + */ + public Grade toModelType() throws IllegalValueException { + return this.grade ? Grade.GRADED : Grade.UNGRADED; + } + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedGradeKey.java b/src/main/java/seedu/address/storage/JsonAdaptedGradeKey.java new file mode 100644 index 00000000000..33f3c80c8ee --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedGradeKey.java @@ -0,0 +1,42 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.grade.GradeKey; +/** + * Jackson-friendly version of {@link GradeKey}. + */ +class JsonAdaptedGradeKey { + + private final JsonAdaptedStudent student; + private final JsonAdaptedTask task; + + /** + * Constructs a {@code JsonAdaptedTask} with the given student and task details. + */ + @JsonCreator + public JsonAdaptedGradeKey(@JsonProperty("student") JsonAdaptedStudent student, + @JsonProperty("task") JsonAdaptedTask task) { + this.student = student; + this.task = task; + } + + /** + * Converts a given {@code GradeKey} into this class for Jackson use. + */ + public JsonAdaptedGradeKey(GradeKey source) { + student = new JsonAdaptedStudent(source.getStudent()); + task = new JsonAdaptedTask(source.getTask()); + } + + /** + * Converts this Jackson-friendly adapted gradeKey object into the model's {@code GradeKey} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted person. + */ + public GradeKey toModelType() throws IllegalValueException { + return new GradeKey(student.toModelType(), task.toModelType()); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedGradeTuple.java b/src/main/java/seedu/address/storage/JsonAdaptedGradeTuple.java new file mode 100644 index 00000000000..ea7e2a9c159 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedGradeTuple.java @@ -0,0 +1,43 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.model.grade.Grade; +import seedu.address.model.grade.GradeKey; +import seedu.address.model.student.GradeMap; + +/** + * Jackson-friendly of a {@link GradeMap} tuple + */ +class JsonAdaptedGradeTuple { + + private final JsonAdaptedGradeKey gradeKey; + private final JsonAdaptedGrade grade; + + /** + * Constructs a {@code JsonAdaptedTask} with the given person details. + */ + @JsonCreator + public JsonAdaptedGradeTuple(@JsonProperty("gradeKey") JsonAdaptedGradeKey gradeKey, + @JsonProperty("grade") JsonAdaptedGrade grade) { + this.gradeKey = gradeKey; + this.grade = grade; + } + + /** + * Converts a given {@code (GradeKey, Grade)} into this class for Jackson use. + */ + public JsonAdaptedGradeTuple(GradeKey gradeKey, Grade grade) { + this.gradeKey = new JsonAdaptedGradeKey(gradeKey); + this.grade = new JsonAdaptedGrade(grade); + } + + public JsonAdaptedGradeKey getGradeKey() { + return gradeKey; + } + + public JsonAdaptedGrade getGrade() { + return grade; + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedStudent.java similarity index 58% rename from src/main/java/seedu/address/storage/JsonAdaptedPerson.java rename to src/main/java/seedu/address/storage/JsonAdaptedStudent.java index a6321cec2ea..52eec487a62 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedStudent.java @@ -10,66 +10,74 @@ 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.student.Email; +import seedu.address.model.student.Name; +import seedu.address.model.student.Phone; +import seedu.address.model.student.Student; +import seedu.address.model.student.TutorialGroup; import seedu.address.model.tag.Tag; /** - * Jackson-friendly version of {@link Person}. + * Jackson-friendly version of {@link Student}. */ -class JsonAdaptedPerson { +class JsonAdaptedStudent { 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 String tutorialGroup; private final List tagged = new ArrayList<>(); /** - * Constructs a {@code JsonAdaptedPerson} with the given person details. + * Constructs a {@code JsonAdaptedStudent} with the given student details. */ @JsonCreator - public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, - @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tagged") List tagged) { + public JsonAdaptedStudent(@JsonProperty("name") String name, @JsonProperty("phone") String phone, + @JsonProperty("email") String email, + @JsonProperty("tutorialGroup") String tutorialGroup, + @JsonProperty("tagged") List tagged) { this.name = name; this.phone = phone; this.email = email; - this.address = address; + if (tutorialGroup != null) { + this.tutorialGroup = tutorialGroup; + } else { + this.tutorialGroup = null; + } if (tagged != null) { this.tagged.addAll(tagged); } } /** - * Converts a given {@code Person} into this class for Jackson use. + * Converts a given {@code Student} into this class for Jackson use. */ - public JsonAdaptedPerson(Person source) { + public JsonAdaptedStudent(Student source) { name = source.getName().fullName; phone = source.getPhone().value; email = source.getEmail().value; - address = source.getAddress().value; + if (source.getTutorialGroup() != null) { + tutorialGroup = source.getTutorialGroup().toString(); + } else { + tutorialGroup = null; + } 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. + * 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 person. + * @throws IllegalValueException if there were any data constraints violated in the adapted student. */ - public Person toModelType() throws IllegalValueException { + public Student 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())); } @@ -94,16 +102,12 @@ public Person toModelType() throws IllegalValueException { } 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); + if (tutorialGroup == null) { + return new Student(modelName, modelPhone, modelEmail, modelTags); + } else { + final TutorialGroup modelTutorialGroup = new TutorialGroup(tutorialGroup); + return new Student(modelName, modelPhone, modelEmail, modelTags, modelTutorialGroup); + } } - } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTask.java b/src/main/java/seedu/address/storage/JsonAdaptedTask.java new file mode 100644 index 00000000000..655704c5861 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedTask.java @@ -0,0 +1,105 @@ +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.Student; +import seedu.address.model.task.Task; +import seedu.address.model.task.TaskDeadline; +import seedu.address.model.task.TaskDescription; +import seedu.address.model.task.TaskName; + +/** + * Jackson-friendly version of {@link Task}. + */ +class JsonAdaptedTask { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Task's %s field is missing!"; + + private final String taskName; + private final String taskDescription; + private final String taskDeadline; + private final List students = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedTask} with the given task details. + */ + @JsonCreator + public JsonAdaptedTask(@JsonProperty("taskName") String taskName, + @JsonProperty("taskDescription") String taskDescription, + @JsonProperty("taskDeadline") String taskDeadline, + @JsonProperty("students") List students) { + this.taskName = taskName; + this.taskDescription = taskDescription; + this.taskDeadline = taskDeadline; + if (students != null) { + this.students.addAll(students); + } + } + + /** + * Converts a given {@code Task} into this class for Jackson use. + */ + public JsonAdaptedTask(Task source) { + taskName = source.getTaskName().taskName; + taskDescription = source.getTaskDescription().description; + taskDeadline = source.getTaskDeadline().toString(); + students.addAll(source.getStudents().stream() + .map(JsonAdaptedStudent::new) + .collect(Collectors.toList())); + } + + /** + * Converts this Jackson-friendly adapted task object into the model's {@code Task} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted task. + */ + public Task toModelType() throws IllegalValueException { + final List studentList = new ArrayList<>(); + for (JsonAdaptedStudent s : students) { + studentList.add(s.toModelType()); + } + + if (taskName == null) { + throw new IllegalValueException(String.format( + MISSING_FIELD_MESSAGE_FORMAT, TaskName.class.getSimpleName() + )); + } + if (!TaskName.isValidName(taskName)) { + throw new IllegalValueException(TaskName.MESSAGE_CONSTRAINTS); + } + final TaskName modelName = new TaskName(taskName); + + if (taskDeadline == null) { + throw new IllegalValueException(String.format( + MISSING_FIELD_MESSAGE_FORMAT, TaskDeadline.class.getSimpleName() + )); + } + if (!TaskDeadline.isInDeadlineFormat(taskDeadline)) { + throw new IllegalValueException(TaskDeadline.MESSAGE_CONSTRAINTS); + } + if (!TaskDeadline.isValidDate(taskDeadline)) { + throw new IllegalValueException(TaskDeadline.MESSAGE_CONSTRAINTS); + } + final TaskDeadline modelDeadline = new TaskDeadline(taskDeadline); + + if (taskDescription == null) { + throw new IllegalValueException(String.format( + MISSING_FIELD_MESSAGE_FORMAT, TaskDescription.class.getSimpleName() + )); + } + + final TaskDescription modelDescription = new TaskDescription(taskDescription); + + final Set modelStudents = new HashSet<>(studentList); + return new Task(modelName, modelDescription, modelDeadline, modelStudents); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTutorialGroup.java b/src/main/java/seedu/address/storage/JsonAdaptedTutorialGroup.java new file mode 100644 index 00000000000..607abd8b6e3 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedTutorialGroup.java @@ -0,0 +1,48 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.student.Name; +import seedu.address.model.student.TutorialGroup; +/** + * Jackson-friendly version of {@link TutorialGroup}. + */ +class JsonAdaptedTutorialGroup { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Tutorial group's %s field is missing!"; + + private final String name; + + //private final List tagged = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedTutorialGroup} with the given tutorial group details. + */ + @JsonCreator + public JsonAdaptedTutorialGroup(@JsonProperty("tutorialName") String name) { + this.name = name; + } + + /** + * Converts a given {@code TutorialGroup} into this class for Jackson use. + */ + public JsonAdaptedTutorialGroup(TutorialGroup source) { + name = source.toString(); + } + + /** + * Converts this Jackson-friendly adapted tutorial group object into the model's {@code TutorialGroup} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted person. + */ + public TutorialGroup toModelType() throws IllegalValueException { + if (name == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + + return new TutorialGroup(name); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java index 5efd834091d..af8dd2aaffa 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java @@ -8,10 +8,15 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonRootName; +import javafx.collections.ObservableMap; 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.grade.Grade; +import seedu.address.model.grade.GradeKey; +import seedu.address.model.student.Student; +import seedu.address.model.student.TutorialGroup; +import seedu.address.model.task.Task; /** * An Immutable AddressBook that is serializable to JSON format. @@ -19,16 +24,25 @@ @JsonRootName(value = "addressbook") class JsonSerializableAddressBook { - public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; + public static final String MESSAGE_DUPLICATE_PERSON = "Students list contains duplicate student(s)."; - private final List persons = new ArrayList<>(); + private final List students = new ArrayList<>(); + private final List tasks = new ArrayList<>(); + private final List groups = new ArrayList<>(); + private final List grades = new ArrayList<>(); /** - * Constructs a {@code JsonSerializableAddressBook} with the given persons. + * Constructs a {@code JsonSerializableAddressBook} with the given students. */ @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { - this.persons.addAll(persons); + public JsonSerializableAddressBook(@JsonProperty("students") List students, + @JsonProperty("tasks") List tasks, + @JsonProperty("groups") List groups, + @JsonProperty("grades") List grades) { + this.students.addAll(students); + this.tasks.addAll(tasks); + this.groups.addAll(groups); + this.grades.addAll(grades); } /** @@ -37,7 +51,15 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List gradeMap = source.getGradeMap(); + for (GradeKey gradeKey : gradeMap.keySet()) { + Grade grade = gradeMap.get(gradeKey); + grades.add(new JsonAdaptedGradeTuple(gradeKey, grade)); + } } /** @@ -47,14 +69,35 @@ public JsonSerializableAddressBook(ReadOnlyAddressBook source) { */ public AddressBook toModelType() throws IllegalValueException { AddressBook addressBook = new AddressBook(); - for (JsonAdaptedPerson jsonAdaptedPerson : persons) { - Person person = jsonAdaptedPerson.toModelType(); - if (addressBook.hasPerson(person)) { + for (JsonAdaptedStudent jsonAdaptedStudent : students) { + Student student = jsonAdaptedStudent.toModelType(); + if (addressBook.hasStudent(student)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); + } + addressBook.addStudent(student); + } + + for (JsonAdaptedTutorialGroup jsonAdaptedTutorialGroup : groups) { + TutorialGroup tutorialGroup = jsonAdaptedTutorialGroup.toModelType(); + if (addressBook.hasTutorialGroup(tutorialGroup)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); + } + addressBook.addTutorialGroup(tutorialGroup); + } + + for (JsonAdaptedTask jsonAdaptedTask : tasks) { + Task task = jsonAdaptedTask.toModelType(); + if (addressBook.hasTask(task)) { throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); } - addressBook.addPerson(person); + addressBook.addTask(task); + } + + for (JsonAdaptedGradeTuple jsonAdaptedGradeTuple : grades) { + GradeKey gradeKey = jsonAdaptedGradeTuple.getGradeKey().toModelType(); + Grade grade = jsonAdaptedGradeTuple.getGrade().toModelType(); + addressBook.addGrade(gradeKey, grade); } return addressBook; } - } diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java index 9e75478664b..27aaab317f0 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/address/ui/CommandBox.java @@ -14,7 +14,7 @@ public class CommandBox extends UiPart { public static final String ERROR_STYLE_CLASS = "error"; - private static final String FXML = "CommandBox.fxml"; + private static final String FXML = "CommandInput.fxml"; private final CommandExecutor commandExecutor; diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/seedu/address/ui/CommandOutput.java similarity index 77% rename from src/main/java/seedu/address/ui/ResultDisplay.java rename to src/main/java/seedu/address/ui/CommandOutput.java index 7d98e84eedf..21817870b80 100644 --- a/src/main/java/seedu/address/ui/ResultDisplay.java +++ b/src/main/java/seedu/address/ui/CommandOutput.java @@ -9,14 +9,14 @@ /** * A ui for the status bar that is displayed at the header of the application. */ -public class ResultDisplay extends UiPart { +public class CommandOutput extends UiPart { - private static final String FXML = "ResultDisplay.fxml"; + private static final String FXML = "CommandOutput.fxml"; @FXML private TextArea resultDisplay; - public ResultDisplay() { + public CommandOutput() { super(FXML); } diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 3f16b2fcf26..d53d6e5603f 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/address/ui/HelpWindow.java @@ -15,7 +15,7 @@ */ public class HelpWindow extends UiPart { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; + public static final String USERGUIDE_URL = "https://ay2223s1-cs2103t-t13-1.github.io/tp/UserGuide.html#quick-start"; public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 9106c3aa6e5..45a0ed4c407 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.HBox; import javafx.scene.layout.StackPane; import javafx.stage.Stage; import seedu.address.commons.core.GuiSettings; @@ -31,24 +32,22 @@ public class MainWindow extends UiPart { private Logic logic; // Independent Ui parts residing in this Ui container - private PersonListPanel personListPanel; - private ResultDisplay resultDisplay; + private StudentListPanel studentListPanel; + private TaskListPanel taskListPanel; + private CommandOutput commandOutput; private HelpWindow helpWindow; @FXML - private StackPane commandBoxPlaceholder; + private HBox commandInputPlaceholder; @FXML private MenuItem helpMenuItem; @FXML - private StackPane personListPanelPlaceholder; + private StackPane resultListPanelPlaceholder; @FXML - private StackPane resultDisplayPlaceholder; - - @FXML - private StackPane statusbarPlaceholder; + private StackPane commandOutputPlaceholder; /** * Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}. @@ -60,6 +59,9 @@ public MainWindow(Stage primaryStage, Logic logic) { this.primaryStage = primaryStage; this.logic = logic; + // Set title + primaryStage.setTitle("Teaching Assistant Assistant"); + // Configure the UI setWindowDefaultSize(logic.getGuiSettings()); @@ -110,17 +112,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()); + // Show a list of students upon startup. + studentListPanel = new StudentListPanel(logic.getFilteredStudentList()); + resultListPanelPlaceholder.getChildren().add(studentListPanel.getRoot()); - resultDisplay = new ResultDisplay(); - resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - - StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); - statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); + commandOutput = new CommandOutput(); + commandOutputPlaceholder.getChildren().add(commandOutput.getRoot()); CommandBox commandBox = new CommandBox(this::executeCommand); - commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); + commandInputPlaceholder.getChildren().add(commandBox.getRoot()); } /** @@ -163,10 +163,38 @@ private void handleExit() { primaryStage.hide(); } - public PersonListPanel getPersonListPanel() { - return personListPanel; + /** + * When the 'Students' button in the sidebar is clicked, + * display a list of students in MainWindow. + */ + @FXML + public void handleClickStudentsButton() { + studentListPanel = new StudentListPanel(logic.getFilteredStudentList()); + resultListPanelPlaceholder.getChildren().clear(); + resultListPanelPlaceholder.getChildren().add(studentListPanel.getRoot()); } + /** + * When the 'Tasks' button in the sidebar is clicked, + * display a list of tasks in MainWindow. + */ + @FXML + public void handleClickTasksButton() { + // A helpful comment goes here. + taskListPanel = new TaskListPanel(logic.getFilteredTaskList(), logic.getGradeMap()); + resultListPanelPlaceholder.getChildren().clear(); + resultListPanelPlaceholder.getChildren().add(taskListPanel.getRoot()); + } + + public StudentListPanel getStudentListPanel() { + return studentListPanel; + } + + public TaskListPanel getTaskListPanel() { + return taskListPanel; + } + + /** * Executes the command and returns the result. * @@ -176,7 +204,7 @@ private CommandResult executeCommand(String commandText) throws CommandException try { CommandResult commandResult = logic.execute(commandText); logger.info("Result: " + commandResult.getFeedbackToUser()); - resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser()); + commandOutput.setFeedbackToUser(commandResult.getFeedbackToUser()); if (commandResult.isShowHelp()) { handleHelp(); @@ -189,7 +217,7 @@ private CommandResult executeCommand(String commandText) throws CommandException return commandResult; } catch (CommandException | ParseException e) { logger.info("Invalid command: " + commandText); - resultDisplay.setFeedbackToUser(e.getMessage()); + commandOutput.setFeedbackToUser(e.getMessage()); throw e; } } 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 deleted file mode 100644 index b577f829423..00000000000 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ /dev/null @@ -1,28 +0,0 @@ -package seedu.address.ui; - -import java.nio.file.Path; -import java.nio.file.Paths; - -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.layout.Region; - -/** - * A ui for the status bar that is displayed at the footer of the application. - */ -public class StatusBarFooter extends UiPart { - - private static final String FXML = "StatusBarFooter.fxml"; - - @FXML - private Label saveLocationStatus; - - /** - * Creates a {@code StatusBarFooter} with the given {@code Path}. - */ - public StatusBarFooter(Path saveLocation) { - super(FXML); - saveLocationStatus.setText(Paths.get(".").resolve(saveLocation).toString()); - } - -} diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/StudentListCard.java similarity index 51% rename from src/main/java/seedu/address/ui/PersonCard.java rename to src/main/java/seedu/address/ui/StudentListCard.java index 7fc927bc5d9..a155a47736b 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/StudentListCard.java @@ -5,16 +5,16 @@ 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; +import javafx.scene.layout.VBox; +import seedu.address.model.student.Student; /** - * An UI component that displays information of a {@code Person}. + * An UI component that displays information of a {@code Student}. */ -public class PersonCard extends UiPart { +public class StudentListCard extends UiPart { - private static final String FXML = "PersonListCard.fxml"; + private static final String FXML = "StudentListCard.fxml"; /** * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. @@ -24,35 +24,40 @@ public class PersonCard extends UiPart { * @see The issue on AddressBook level 4 */ - public final Person person; + public final Student student; @FXML - private HBox cardPane; + private VBox cardPane; @FXML private Label name; @FXML private Label id; @FXML - private Label phone; + private Label tutorialGroup; @FXML - private Label address; + private Label phone; @FXML private Label email; @FXML - private FlowPane tags; + private FlowPane tags; // Contains Label objects. /** - * Creates a {@code PersonCode} with the given {@code Person} and index to display. + * Creates a {@code StudentCode} with the given {@code Student} and index to display. */ - public PersonCard(Person person, int displayedIndex) { + public StudentListCard(Student student, 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() + this.student = student; + id.setText(displayedIndex + "."); + if (student.getTutorialGroup() != null) { + tutorialGroup.setText(student.getTutorialGroup().toString()); + } else { + tutorialGroup.setText("No tutorial group yet"); + } + name.setText(this.student.getName().fullName); + phone.setText(this.student.getPhone().value); + email.setText(this.student.getEmail().value); + + this.student.getTags().stream() .sorted(Comparator.comparing(tag -> tag.tagName)) .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); } @@ -65,13 +70,13 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof PersonCard)) { + if (!(other instanceof StudentListCard)) { return false; } // state check - PersonCard card = (PersonCard) other; + StudentListCard card = (StudentListCard) other; return id.getText().equals(card.id.getText()) - && person.equals(card.person); + && 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..9bdecc99c60 --- /dev/null +++ b/src/main/java/seedu/address/ui/StudentListPanel.java @@ -0,0 +1,49 @@ +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 students. + */ +public class StudentListPanel extends UiPart { + private static final String FXML = "StudentListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(StudentListPanel.class); + + @FXML + private ListView studentListView; + + /** + * Creates a {@code StudentListPanel} with the given {@code ObservableList}. + */ + public StudentListPanel(ObservableList studentList) { + super(FXML); + studentListView.setItems(studentList); + studentListView.setCellFactory(listView -> new StudentListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Student} using a {@code StudentListCard}. + */ + static class StudentListViewCell extends ListCell { + @Override + protected void updateItem(Student student, boolean empty) { + super.updateItem(student, empty); + + if (empty || student == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new StudentListCard(student, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/TaskListCard.java b/src/main/java/seedu/address/ui/TaskListCard.java new file mode 100644 index 00000000000..eeb8cc04a48 --- /dev/null +++ b/src/main/java/seedu/address/ui/TaskListCard.java @@ -0,0 +1,141 @@ +package seedu.address.ui; + +import java.util.Set; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import seedu.address.model.grade.Grade; +import seedu.address.model.grade.GradeKey; +import seedu.address.model.student.Student; +import seedu.address.model.task.Task; + +/** + * An UI component that displays information of a {@code Task}. + */ +public class TaskListCard extends UiPart { + + private static final String FXML = "TaskListCard.fxml"; + + public final Task task; + + @FXML + private VBox cardPane; // Think of this as the entire TaskListCard. + @FXML + private Label id; + @FXML + private Label name; + @FXML + private Label description; + @FXML + private Label deadline; + @FXML + private Label completion; + private boolean isExpanded; + @FXML + private VBox optionalInfo; // This is the optional information to be displayed. + @FXML + private Label studentsHeading; + @FXML + private Label studentsBodyText; + + /** + * Creates a {@code TaskListCard} with the given {@code Task} and index to display. + */ + public TaskListCard(Task task, int displayedIndex) { + super(FXML); + + this.task = task; + id.setText(displayedIndex + "."); + name.setText(String.valueOf(task.getTaskName())); + description.setText(String.valueOf(task.getTaskDescription())); + deadline.setText(String.format("Due by %s", task.getTaskDeadline())); + + Set setOfStudents = task.getStudents(); + final var gradesMap = Task.getGradesMap(); + + if (setOfStudents.size() == 0) { + + completion.setText("No students are assigned to this task."); + studentsHeading.setVisible(false); + studentsBodyText.setVisible(false); + } else { + + // Calculate how many percent complete this task is. + int denominator = setOfStudents.size(); + assert denominator > 0; + int numerator = 0; + + for (Student student : setOfStudents) { + if (gradesMap.get(new GradeKey(student, task)) == Grade.GRADED) { + numerator += 1; + } + } + int percentageCompletion = 100 * numerator / denominator; + completion.setText( + String.format("%d%% completed (%d/%d) students", percentageCompletion, numerator, denominator) + ); + + + studentsHeading.setText("List of Students:"); + + // For each student, display whether the task is complete for this student. + StringBuilder studentsBodyTextString = new StringBuilder(); + for (Student student : setOfStudents) { + boolean taskCompletedForThisStudent = gradesMap.get(new GradeKey(student, task)) == Grade.GRADED; + studentsBodyTextString.append(String.format( + "[%s] %s\n", taskCompletedForThisStudent ? "X" : " ", student.getName()) + ); + } + studentsBodyText.setText(studentsBodyTextString.toString()); + } + + // TaskListCards are created when the "Tasks" button is clicked. + // At this point, no TaskListCard is expanded. + this.isExpanded = false; + optionalInfo.setVisible(false); + optionalInfo.setManaged(false); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof TaskListCard)) { + return false; + } + + // state check + TaskListCard card = (TaskListCard) other; + return id.getText().equals(card.id.getText()) + && task.equals(card.task); + } + + /** + * When the {@code TaskListCard} is clicked, it toggles between showing and not showing the students for whom + * the {@code Task} has been completed. However, if no {@code Student}s are assigned to the {@code Task}, then the + * {@code TaskListCard} will not expand, since there is no information to display. + */ + @FXML + public void onCardClicked() { + + // If the task has no students, then there is no additional information to display. + // Block any expansion and contraction. + if (this.task.getStudents().size() == 0) { + return; + } + + // Toggle between showing and not showing the students for whom the task has been completed. + isExpanded = !isExpanded; + optionalInfo.setVisible(isExpanded); + optionalInfo.setManaged(isExpanded); + + // Update the UI, now that the optional information's visibility has changed the height of the card. + cardPane.requestLayout(); + } +} diff --git a/src/main/java/seedu/address/ui/TaskListPanel.java b/src/main/java/seedu/address/ui/TaskListPanel.java new file mode 100644 index 00000000000..03103daa9a9 --- /dev/null +++ b/src/main/java/seedu/address/ui/TaskListPanel.java @@ -0,0 +1,59 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.collections.ObservableMap; +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.grade.Grade; +import seedu.address.model.grade.GradeKey; +import seedu.address.model.task.Task; + +/** + * Panel containing the list of tasks. + */ +public class TaskListPanel extends UiPart { + private static final String FXML = "TaskListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(TaskListPanel.class); + + @FXML + private ListView taskListView; + + /** + * Creates a {@code TaskListPanel} with the given {@code ObservableList}. + */ + public TaskListPanel(ObservableList taskList, ObservableMap gradeMap) { + super(FXML); + + taskListView.setItems(taskList); + taskListView.setCellFactory(listView -> new TaskListViewCell()); + taskListView.getSelectionModel().getSelectedItems().addListener(new ListChangeListener() { + + @Override + public void onChanged(Change c) { + taskListView.refresh(); + } + }); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Task} using a {@code TaskListCard}. + */ + static class TaskListViewCell extends ListCell { + @Override + protected void updateItem(Task task, boolean empty) { + super.updateItem(task, empty); + if (empty || task == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new TaskListCard(task, getIndex() + 1).getRoot()); + } + } + } +} diff --git a/src/main/resources/view/BrightTheme.css b/src/main/resources/view/BrightTheme.css new file mode 100644 index 00000000000..aa44a091127 --- /dev/null +++ b/src/main/resources/view/BrightTheme.css @@ -0,0 +1,27 @@ +.list-cell { + -fx-background-color: white; + -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: #efefef; +} + +.list-cell:filled:odd { + -fx-background-color: #dfdfdf; +} + +.list-cell:filled:selected { + -fx-background-color: #0096ff; +} + +.list-cell:filled:selected #cardPane { + -fx-border-color: #0096ff; + -fx-border-width: 1; +} + +* { + -fx-font-family: "Comic Sans MS"; +} diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml deleted file mode 100644 index 09f6d6fe9e4..00000000000 --- a/src/main/resources/view/CommandBox.fxml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/main/resources/view/CommandInput.fxml b/src/main/resources/view/CommandInput.fxml new file mode 100644 index 00000000000..3d009cf469e --- /dev/null +++ b/src/main/resources/view/CommandInput.fxml @@ -0,0 +1,10 @@ + + + + + + + + +