diff --git a/README.md b/README.md index 13f5c77403f..46c0257d6c0 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,18 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) +[![CI Status](https://github.com/AY2021S1-CS2103-T14-1/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2021S1-CS2103-T14-1/tp/actions) + +# FaculType ![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. +**FaculType** is a desktop app for managing faculty members and their modules, optimized for use via a Command Line + Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). + + In the current version, **FaculType** targets faculty leaders of universities who are responsible for managing faculty members and modules. + +# Site Map +* [User Guide](https://github.com/AY2021S1-CS2103-T14-1/tp/blob/master/docs/UserGuide.md) +* [Developer Guide](https://github.com/AY2021S1-CS2103-T14-1/tp/blob/master/docs/DeveloperGuide.md) +* [About Us](https://github.com/AY2021S1-CS2103-T14-1/tp/blob/master/docs/AboutUs.md) + +# 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 be2d2905dde..7e5edc23018 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,10 @@ checkstyle { toolVersion = '8.29' } +run { + enableAssertions = true +} + test { useJUnitPlatform() finalizedBy jacocoTestReport @@ -66,7 +70,13 @@ dependencies { } shadowJar { - archiveName = 'addressbook.jar' + archiveName = 'FaculType.jar' +} + +gradle.projectsEvaluated { + tasks.withType(JavaCompile) { + options.compilerArgs << "-Xlint:unchecked" + } } defaultTasks 'clean', 'test' diff --git a/copyright.txt b/copyright.txt index 93aa2a39ce2..9ad9811131f 100644 --- a/copyright.txt +++ b/copyright.txt @@ -7,3 +7,6 @@ Copyright by Susumu Yoshida - http://www.mcdodesign.com/ Copyright by Jan Jan Kovařík - http://glyphicons.com/ - calendar.png - edit.png + +Copyright by Justin Tzuriel Krisnahadi +- facultype_icon.png diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 1c9514e966a..0768b02e85e 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -9,51 +9,52 @@ You can reach us at the email `seer[at]comp.nus.edu.sg` ## Project team -### John Doe +### Justin Tzuriel Krisnahadi - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/justintzuriel)] +[[portfolio](team/justintzuriel.md)] -* Role: Project Advisor +* Role: Team Lead +* Responsibilities: Deliverables and deadlines -### Jane Doe +### Christian Drake Martin - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/drake25122000)] +[[portfolio](team/drake25122000.md)] -* Role: Team Lead -* Responsibilities: UI +* Role: Developer +* Responsibilities: Code quality -### Johnny Doe +### Erin May Gunawan - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](http://github.com/erinmayg)] +[[portfolio](team/erinmayg.md)] * Role: Developer -* Responsibilities: Data +* Responsibilities: UI -### Jean Doe +### Florencia Martina - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/florenciamartina)] +[[portfolio](team/florenciamartina.md)] * Role: Developer -* Responsibilities: Dev Ops + Threading +* Responsibilities: Documentation -### James Doe +### Woo Jian Zhe - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/jzwoo)] +[[portfolio](team/jzwoo.md)] * Role: Developer -* Responsibilities: UI +* Responsibilities: Testing diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 4829fe43011..bbcc1f3585d 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -19,15 +19,17 @@ Refer to the guide [_Setting up and getting started_](SettingUp.md). +
+ The ***Architecture Diagram*** given above explains the high-level design of the App. Given below is a quick overview of each component.
-: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/AY2021S1-CS2103-T14-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.
-**`Main`** has two classes called [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java). It is responsible for, +**`Main`** has two classes called [`Main`](https://github.com/AY2021S1-CS2103-T14-1/tp/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/AY2021S1-CS2103-T14-1/tp/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. @@ -47,6 +49,8 @@ Each of the four components, For example, the `Logic` component (see the class diagram given below) defines its API in the `Logic.java` interface and exposes its functionality using the `LogicManager.java` class which implements the `Logic` interface. +
+ ![Class Diagram of the Logic Component](images/LogicClassDiagram.png) **How the architecture components interact with each other** @@ -57,35 +61,41 @@ The *Sequence Diagram* below shows how the components interact with each other f The sections below give more details of each component. +
+ ### UI component ![Structure of the UI Component](images/UiClassDiagram.png) **API** : -[`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java) +[`Ui.java`](https://github.com/AY2021S1-CS2103-T14-1/tp/tree/master/src/main/java/seedu/address/ui/Ui.java) 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. -The `UI` component uses 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 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/AY2021S1-CS2103-T14-1/tp/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2021S1-CS2103-T14-1/tp/tree/master/src/main/resources/view/MainWindow.fxml) The `UI` component, * Executes user commands using the `Logic` component. * Listens for changes to `Model` data so that the UI can be updated with the modified data. +
+ ### Logic component ![Structure of the Logic Component](images/LogicClassDiagram.png) **API** : -[`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java) +[`Logic.java`](https://github.com/AY2021S1-CS2103-T14-1/tp/tree/master/src/main/java/seedu/address/logic/Logic.java) 1. `Logic` uses the `AddressBookParser` class to parse the user command. 1. This results in a `Command` object which is executed by the `LogicManager`. -1. The command execution can affect the `Model` (e.g. adding a person). +1. The command execution can affect the `Model` (e.g. adding a contact). 1. The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui`. 1. In addition, the `CommandResult` object can also instruct the `Ui` to perform certain actions, such as displaying help to the user. +
+ Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete 1")` API call. ![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png) @@ -93,17 +103,22 @@ Given below is the Sequence Diagram for interactions within the `Logic` componen
:information_source: **Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
+
+ ### Model component ![Structure of the Model Component](images/ModelClassDiagram.png) -**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/AY2021S1-CS2103-T14-1/tp/tree/master/src/main/java/seedu/address/model/Model.java) + +
The `Model`, * stores a `UserPref` object that represents the user’s preferences. -* stores the address book data. -* exposes 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 FaculType data. +* exposes an unmodifiable `ObservableList` and `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. * does not depend on any of the other three components. @@ -117,11 +132,13 @@ The `Model`, ![Structure of the Storage Component](images/StorageClassDiagram.png) -**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/AY2021S1-CS2103-T14-1/tp/tree/master/src/main/java/seedu/address/storage/Storage.java) + +
The `Storage` component, * can save `UserPref` objects in json format and read it back. -* can save the address book data in json format and read it back. +* can save FaculType data in json format and read it back. ### Common classes @@ -129,41 +146,358 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa -------------------------------------------------------------------------------------------------------------------- +
+ ## **Implementation** This section describes some noteworthy details on how certain features are implemented. +### Find contacts by attributes feature + +#### Implementation + +The find mechanism is facilitated by `FindCommand` and `FindCommandParser`. It allows users to search for contacts by + their respective attributes. It uses `ModelManager#updateFilteredPersonList(Predicate p)` which is + exposed in the `Model` interface as `Model#updateFilteredList(Predicate p)`. The method updates the + current person list and filters it according to the given predicate which will then be shown accordingly in the GUI. + +The following sequence diagram shows how the find by attributes operation works: + +![FindSequenceDiagram](images/FindSequenceDiagram.png) +
:information_source: **Note:** The lifeline for `FindCommandParser` 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 a user executes a find command: + +![FindActivityDiagram](images/FindActivityDiagram.png) + +#### Design consideration: + +##### Aspect: How find executes + +* **Alternative 1 (current choice):** AND Searching from multiple attributes and keywords. + * Pros: Provides the ability to narrow down search results by adding more keywords and attributes. + * Cons: Unable to search for multiple persons with different attributes. + +* **Alternative 2:** AND Searching across attributes, OR Searching between keywords. + * Pros: Provides the ability to narrow down search results by adding more attributes as well as expanding the search + scope by adding more keywords (i.e: more flexible). + * Cons: A possible source of confusion as they use different searching methods. + +Both options are equally feasible. However, Alternative 1 was chosen to avoid confusion for prospective users. + +
+ +### Find modules by attributes feature + +#### Implementation + +The find module mechanism is facilitated by `FindModCommand` and `FindModCommandParser`. It allows users to search for modules based on their respective attributes which are the module code, module name and instructors of the module. +It uses `ModelManager#updateFilteredModuleList(Predicate p)` which is exposed in the Model interface as `Model#updateFilteredModuleList(Predicate p)`. +The method updates the active semester module list and filters it according to the given predicate which will then be reflected accordingly in the GUI. + +The following sequence diagram shows how the find module by module attributes operation works: + +![FindmodSequenceDiagram](images/FindmodSequenceDiagram.png) + +
:information_source: **Note:** The lifeline for `FindModCommandParser` 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 a user executes a findmod command: + +![FindmodActivityDiagram](images/FindmodActivityDiagram.png) + +#### Design consideration: + +##### Aspect: How findmod executes + +* **Alternative 1 (current choice):** AND searching from multiple attributes and AND searching between keywords for module name and instructor attributes. Module code attribute only allows single keywords. + * Pros : Provides the ability to narrow down the search results by adding more attributes and keywords. Single keyword for module code attribute allows for more focused module code searches. + * Cons : Unable to have a more general search and unable to search for multiple modules with different module codes. + +* **Alternative 2:** And searching across attributes and OR searching between keywords for module name and instructor attributes. + * Pros : Provides the ability for a very general and flexible search. + * Cons : Unable to have a more focused search, might be more confusing for the user to narrow down his/her searches. + +
+ +### Delete modules feature + +#### Implementation + +The delete module mechanism is facilitated by the `DelmodCommand` and the `DelmodCommandParser`. +It uses an operation `AddressBook#removeModule()` which is exposed in the `Model` interface as `Model#deleteModule()`. +Then, the `removeModuleWithCode()` operation is called in the active semester `UniqueModuleList`. `UniqueModuleList#removeModuleWithCode()` will remove the module with the specified code +from the module list. + +Given below is the example usage scenario and how the delete module mechanism behaves at each step. + +1. The user launches the application. FaculType has the module `CS2103` in the active semester. + +2. The user executes the command `delmod m/CS2103` to delete the module with the module code `CS2103`. + +3. The `DelmodCommand` then calls `Model#deleteModule()` after checking for the existence of the specified module. + +4. The module with the specified module code will be deleted from the active semester `UniqueModuleList` in `AddressBook`. + +
+ +The following sequence diagram shows how the deleting of the module works: + +![DelmodSequenceDiagram](images/DelmodSequenceDiagram.png) + +
:information_source: **Note:** The lifeline for `DelmodCommandParser` 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 a user executes a delmod command: + +![DelmodActivityDiagram](images/DelmodActivityDiagram.png) + +
+ +#### Design consideration: + +##### Aspect: What the delmod command deletes by +* **Alternative 1 (current choice):** Deletes a module based on the module code. + * Pros : More intuitive to the Dean to delete by the module code. + * Cons : Would be more troublesome to look for the module should the Dean forget the module code. + +* **Alternative 2:** Deletes a module based on the index of the module list. + * Pros : Dean does not have to memorise all the module code, can simply delete based on what is shown in the module list. + * Cons : Less intuitive. + +### Assign feature + +#### Implementation + +The assign feature is facilitated by `AssignCommand` and `AssignCommandParser`. +It uses an operation `AddressBook#assignInstructor()` which is exposed in the `Model` interface as `Model#assignInstructor()`. +Then, the `assignInstructor()` operation is called in both `UniqueModuleList` and `Module`. `Module#assignInstructor()` will add the instructor to the module's set of instructors. + +
+ +The following sequence diagram shows how the assign operation works: + +![AssignSequenceDiagram](images/AssignSequenceDiagram.png) + +
:information_source: **Note:** The lifeline for `AssignCommandParser` 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 a user executes an assign command: + +![AssignActivityDiagram](images/AssignActivityDiagram.png) + +
+ +#### Design consideration: + +##### Aspect: How to store assignments +* **Alternative 1 (current choice):** A module has a set of instructors assigned to it. + * Pros: More efficient to list the instructors of a certain module. + * Cons: Less efficient to list the modules of a certain instructor. + +* **Alternative 2:** An instructor has a set of modules they are assigned to. + * Pros: More efficient to list the modules of a certain instructor. + * Cons: Less efficient to list the instructors of a certain module. + +Both are equally viable options but Alternative 1 was chosen so `Person` would not have to be redesigned or have too many fields. + +### Unassign feature + +The unassign feature is facilitated by `UnassignCommand` and `UnassignCommandParser`. +It uses an operation `AddressBook#unassignInstructor()` which is exposed in the `Model` interface as `Model#unassignInstructor()`. +Then, the `unassignInstructor()` operation is called in both `UniqueModuleList` and `Module`. `Module#unassignInstructor()` will remove the instructor from the module's set of instructors. + +
+ +The following sequence diagram shows how the unassign operation works: + +![UnassignSequenceDiagram](images/UnassignSequenceDiagram.png) + +
:information_source: **Note:** The lifeline for `UnassignCommandParser` 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 a user executes an unassign command: + +![UnassignActivityDiagram](images/UnassignActivityDiagram.png) + +
+ +### Unassignall feature + +The unassignall feature is facilitated by `UnassignallCommand` and `UnassignallCommandParser`. +It uses an operation `AddressBook#unassignAllInstructors()` which is exposed in the `Model` interface as `Model#unassignAllInstructors()`. +Then, the `unassignAllInstructors()` operation is called in both `UniqueModuleList` and `Module`. `Module#unassignAllInstructors()` will remove all instructors from all modules' set of instructors. + +The following sequence diagram shows how the unassignall operation works: + +![UnassignallSequenceDiagram](images/UnassignallSequenceDiagram.png) + +
:information_source: **Note:** The lifeline for `UnassignallCommandParser` 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 a user executes an unassignall command: + +![UnassignallActivityDiagram](images/UnassignallActivityDiagram.png) + +#### Design consideration: + +##### Aspect: How unassignall executes + +* **Alternative 1 (current choice):** Unassigns all instructors from all modules. + * Pros : More efficient to unassign all instructors from all modules. + * Cons : Less efficient to unassign a certain instructor from all modules he/she instructs. + +* **Alternative 2:** Unassign a certain instructor from all modules he/she instructs. + * Pros : More efficient to unassign a certain instructor from all modules he/she instructs. + * Cons : Less efficient to unassign all instructors from all modules. + +
+ +### Clear all contacts feature + +#### Implementation + +It implements the following operations: +* `AddressBook#clearContacts()` — Clear all contacts from the contact list. + +These operations are exposed in the `Model` interface as `Model#clearContacts()` and `UniquePersonList` class as `UniquePersonList#clearAll()` + +The following sequence diagram shows how the cclear operation works: + +![CclearSequenceDiagram](images/CclearSequenceDiagram.png) + +
:information_source: **Note:** The lifeline for `OneWordCommandParser` 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 a user executes a cclear command: + +![CclearActivityDiagram](images/CclearActivityDiagram.png) + +### Clear all modules feature + +#### Implementation + +It implements the following operations: +* `AddressBook#clearMod()` — Clear all modules from the active semester module list. + +These operations are exposed in the `Model` interface as `Model#clearMod()` and `UniqueModuleList` class as `UniqueModuleList#clearAll()` + +
+ +The following sequence diagram shows how the mclear operation works: + +![MclearSequenceDiagram](images/MclearSequenceDiagram.png) + +
:information_source: **Note:** The lifeline for `OneWordCommandParser` 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 a user executes a mclear command: + +![MclearActivityDiagram](images/MclearActivityDiagram.png) + +### Switch active semester feature + +#### Implementation + +The switch feature is facilitated by `AddressBook#switchModuleList()` which is exposed in the `Model` interface as `Model#switchModuleList()`. + +AddressBook has two module lists, one for each semester, and one additional `UniqueModuleList` variable named `activeModules` that stores a reference to the active semester's module list. +`AddressBook#switchModuleList()` toggles which module list is referenced by `activeModules`. +All `AddressBook` operations on `UniqueModuleList` are done on `activeModules`. + +
+ +The following sequence diagram shows how the switch operation works: + +![SwitchSequenceDiagram](images/SwitchSequenceDiagram.png) + +
:information_source: **Note:** The lifeline for `OneWordCommandParser` 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 a user executes a switch command: + +![SwitchActivityDiagram](images/SwitchActivityDiagram.png) + +
+ +#### Design consideration: + +##### Aspect: Setting the active semester +* **Alternative 1 (current choice):** There are two module lists and active semester references one of them. + * Pros: Less code to change. + * Cons: Can only manage the modules in the active semester. + +* **Alternative 2:** There is only one module list and there is a filter to select modules of a particular semester. + * Pros: More efficient to list the modules of a certain instructor. + * Cons: Need to add semester field to modules and commands, will have two copies of the same module if held in both semesters, more code to change. + ### \[Proposed\] Undo/redo feature #### Proposed Implementation 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: -* `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. +* `VersionedAddressBook#commit()` — Saves the current FaculType state in its history. +* `VersionedAddressBook#undo()` — Restores the previous FaculType state from its history. +* `VersionedAddressBook#redo()` — Restores a previously undone FaculType state from its history. These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +
+ 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. The `VersionedAddressBook` will be initialized with the initial FaculType state, and the `currentStatePointer` pointing to that single FaculType state. ![UndoRedoState0](images/UndoRedoState0.png) -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. +Step 2. The user executes `delete 5` command to delete the 5th contact in FaculType. The `delete` command calls + `Model#commitAddressBook()`, causing the modified state of FaculType after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted FaculType state. ![UndoRedoState1](images/UndoRedoState1.png) -Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. +
+ +Step 3. The user executes `add n/David …​` to add a new contact. The `add` command also calls `Model#commitAddressBook +()`, causing another modified FaculType state to be saved into the `addressBookStateList`. ![UndoRedoState2](images/UndoRedoState2.png) -
: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`. +
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the FaculType state will not be saved into the `addressBookStateList`.
-Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. +Step 4. The user now decides that adding the contact 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 FaculType state, and restores FaculType to that state. ![UndoRedoState3](images/UndoRedoState3.png) @@ -172,6 +506,8 @@ than attempting to perform the undo.
+
+ The following sequence diagram shows how the undo operation works: ![UndoSequenceDiagram](images/UndoSequenceDiagram.png) @@ -180,20 +516,24 @@ The following sequence diagram shows how the undo operation works: -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. +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 FaculType to that state. -
: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. +
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest FaculType 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.
-Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. +
+ +Step 5. The user then decides to execute the command `list`. Commands that do not modify FaculType, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. ![UndoRedoState4](images/UndoRedoState4.png) -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. +Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all FaculType 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. ![UndoRedoState5](images/UndoRedoState5.png) +
+ The following activity diagram summarizes what happens when a user executes a new command: ![CommitActivityDiagram](images/CommitActivityDiagram.png) @@ -202,24 +542,59 @@ The following activity diagram summarizes what happens when a user executes a ne ##### Aspect: How undo & redo executes -* **Alternative 1 (current choice):** Saves the entire address book. +* **Alternative 1 (current choice):** Saves the entire FaculType data. * Pros: Easy to implement. * Cons: May have performance issues in terms of memory usage. * **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. + * Pros: Will use less memory (e.g. for `delete`, just save the contact being deleted). + * Cons: We must ensure that the implementation of each individual command is correct. + +-------------------------------------------------------------------------------------------------------------------- + +
+ +## **Current bugs and feature ideas** + +This section describes bugs found in the current version of FaculType and +feature ideas that have been proposed but currently don't have implementation details yet. +Both of which are to be fixed/implemented in the next version of FaculType. + +### Current bugs + +* Editing a contact's identifying attributes (name, phone, email) to be the same as another contact's causes problems due to the detection of duplicate contacts. +This bug is inherited from the parent project AddressBook Level 3. +* A tag in a contact card and an instructor tag in a module card may be cut off if the text inside is too long or if there are too many tags. +This bug is inherited from the parent project AddressBook Level 3. + +
+ +Example of the tag bug: + +![TagBug](images/TagBug.png) + +
+ +* No validation of instructor existence for modules. Adding a non-existent instructor by editing the +`addressbook.json` file will not throw any error. + +Example of the instructor bug: -_{more aspects and alternatives to be added}_ +![JSONFileComposite](images/JSONFileComposite.png) -### \[Proposed\] Data archiving -_{Explain here how the data archiving feature will be implemented}_ +![InstructorBug](images/InstructorBug.png) +### Feature ideas + +* Order filter results of contacts and modules by how much they match the specified parameters (Coming soon). +* Differentiate instructor tags in module cards for instructors with the same name (Coming soon). -------------------------------------------------------------------------------------------------------------------- +
+ ## **Documentation, logging, testing, configuration, dev-ops** * [Documentation guide](Documentation.md) @@ -230,127 +605,1038 @@ _{Explain here how the data archiving feature will be implemented}_ -------------------------------------------------------------------------------------------------------------------- +
+ ## **Appendix: Requirements** ### Product scope **Target user profile**: -* has a need to manage a significant number of contacts +* works as a faculty leader +* has a need to manage a significant number of faculty members +* has a need to manage a significant number of modules +* has a need to manage assignments of modules to faculty members * 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 faculty members and modules faster than a typical mouse/GUI driven app ### 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 | +| `* * *` | user | add a new contact | | +| `* * *` | user | delete a contact | remove contacts that I no longer need | + +
+ +| Priority | As a …​ | I want to …​ | So that I can…​ | +| -------- | ------------------------| -----------------------------------|--------------------------------------------------------------------------------------| +| `* * *` | user | find a contact by attributes | locate details of contacts without having to go through the entire list | +| `* * *` | forgetful user | add remarks to contacts | remember certain details about them | +| `* * *` | faculty leader | store a contact's office | keep track of where to find them | +| `* * *` | faculty leader | store a contact's department | keep track of their respective field | +| `* * *` | faculty leader | edit a contact's office | keep the contact data up to date | +| `* * *` | faculty leader | edit a contact's department | keep the contact data up to date | +| `* * *` | faculty leader | add a new module | | +| `* * *` | faculty leader | delete a module | remove modules no longer offered | +| `* * *` | faculty leader | find modules by attributes | locate modules without having to go through the entire list | +| `* * *` | faculty leader | find modules by the instructor's name | locate modules instructed by the instructor | +| `* * *` | faculty leader | assign a contact to various modules | keep track of the modules they instruct | + +
+ +| Priority | As a …​ | I want to …​ | So that I can…​ | +| -------- | ------------------------| -----------------------------------|--------------------------------------------------------------------------------------| +| `* * *` | faculty leader | unassign a contact from various modules | update the assignment data if they no longer instruct those modules | +| `* * *` | faculty leader | unassign all instructors from all modules | quickly reset the assignment data if I need to change most of the assignments | +| `* * *` | user | clear all contacts | | +| `* * *` | faculty leader | clear all modules | discard all the semester's information | +| `* * *` | faculty leader | be able to switch semesters easily | manage the other semester without having to reassign instructors | + +
### 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 `FaculType` and the **Actor** is the `user`, unless specified otherwise) + +**Use case: Listing all contacts and modules** + +**MSS** + +1. User requests to list all contacts and modules. +2. FaculType shows all contacts and modules in the active semester. + + Use case ends. + +**Use case: Listing all contacts** + +**MSS** + +1. User requests to list all contacts. +2. FaculType shows all contacts. + + Use case ends. + +**Use case: Listing all modules** + +**MSS** + +1. User requests to list all modules. +2. FaculType shows all modules in the active semester. + + Use case ends. -**Use case: Delete a person** +**Use case: Switch active semester** **MSS** -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +1. User requests to switch the active semester +2. FaculType updates the module list to show modules active in the other semester + + Use case ends. + +
+ +**Use case: Clearing all contacts** + +**MSS** +1. User requests to clear all contacts +2. FaculType deletes all contacts and the module instructor list is empty. + Use case ends. **Extensions** -* 2a. The list is empty. +* 2a. The contact list is empty. + + * 2a1. FaculType shows an error message. + + Use case ends. - Use case ends. +**Use case: Resetting data** -* 3a. The given index is invalid. +**MSS** - * 3a1. AddressBook shows an error message. +1. User requests to reset data. +2. FaculType deletes all contacts and modules. - Use case resumes at step 2. + Use case ends. -*{More to be added}* +**Use case: Clearing all modules** -### Non-Functional Requirements +**MSS** -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. +1. User requests to clear all modules +2. FaculType deletes all modules in the active semester. + + Use case ends. -*{More to be added}* +**Extensions** -### Glossary +* 2a. The module list is empty. + + * 2a1. FaculType shows an error message. + + Use case ends. -* **Mainstream OS**: Windows, Linux, Unix, OS-X -* **Private contact detail**: A contact detail that is not meant to be shared with others +
--------------------------------------------------------------------------------------------------------------------- +**Use case: Asking for help** -## **Appendix: Instructions for manual testing** +**MSS** -Given below are instructions to test the app manually. +1. User requests for help. +2. FaculType shows a pop-up window. -
:information_source: **Note:** These instructions only provide a starting point for testers to work on; -testers are expected to do more *exploratory* testing. + Use case ends. -
+**Use case: Exiting the program** -### Launch and shutdown +**MSS** -1. Initial launch +1. User requests to exit the program. +2. FaculType exits. - 1. Download the jar file and copy into an empty folder + Use case ends. - 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. +**Use case: Add a contact** -1. Saving window preferences +**MSS** - 1. Resize the window to an optimum size. Move the window to a different location. Close the window. +1. User requests to add a specific contact in the list +2. FaculType adds the contact - 1. Re-launch the app by double-clicking the jar file.
- Expected: The most recent window size and location is retained. + Use case ends. -1. _{ more test cases …​ }_ +**Extensions** -### Deleting a person +* 1a. The attributes are in an invalid format. -1. Deleting a person while all persons are being shown + * 1a1. FaculType shows an error message. - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + Use case resumes at step 1. - 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. +* 1b. The contact to be added already exists. - 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. + * 1b1. FaculType shows an error message. - 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
- Expected: Similar to previous. + Use case resumes at step 1. -1. _{ more test cases …​ }_ +
-### Saving data +**Use case: Delete a contact** + +**MSS** + +1. User requests to list contacts +2. FaculType shows a list of contacts +3. User requests to delete a specific contact in the list +4. FaculType deletes the contact + + Use case ends. + +**Extensions** + +* 2a. The list is empty. + + Use case ends. + +* 3a. The given index is invalid. + + * 3a1. FaculType shows an error message. + + Use case resumes at step 2. + +**Use case: Edit a contact** + +**MSS** + +1. User requests to list contacts +2. FaculType shows a list of contacts +3. User requests to edit a specific contact in the list +4. FaculType edits the contact + + Use case ends. + +
+ +**Extensions** + +* 2a. The list is empty. + + Use case ends. + +* 3a. The given index is invalid. + + * 3a1. FaculType shows an error message. + + Use case resumes at step 2. + +* 3b. The attributes to be edited are invalid. + + * 3b1. FaculType shows an error message. + + Use case resumes at step 2. + +**Use case: Add or update a remark** + +**MSS** + +1. User requests to list contacts +2. FaculType shows a list of contacts +3. User requests to add/edit a specific contact's remark in the list +4. FaculType adds/edits the contact's remark + + Use case ends. + +**Extensions** + +* 2a. The list is empty. + + Use case ends. + +* 3a. The given index is invalid. + + * 3a1. FaculType shows an error message. + + Use case resumes at step 2. + +
+ +**Use case: Find contact(s)** + +**MSS** + +1. User requests to list contacts +2. FaculType shows the list of contacts +3. User requests to find contact(s) by their attributes +4. FaculType shows a list of contacts that fulfills all constraints specified + + Use case ends. + +**Extensions** + +* 2a. The contact list is empty. + + Use case ends. + +* 3a. The user's keywords are invalid. + + * 3a1. FaculType shows an error message. + + Use case resumes at step 2. + +**Use case: Add a module** + +**MSS** + +1. User requests to add a module to the module list +2. FaculType adds the module + + Use case ends. + +**Extensions** + +* 1a. The attributes are in an invalid format. + + * 1a1. FaculType shows an error message. + + Use case resumes at step 1. + +* 1b. The module code already exists. + + * 1b1. FaculType shows an error message. + + Use case resumes at step 1. + +
+ +**Use case: Delete a module** + +**MSS** + +1. FaculType shows a list of modules +2. User requests to delete a module +3. FaculType deletes the module + + Use case ends. + +**Extensions** + +* 1a. The module list is empty. + + Use case ends. + +* 2a. The given module code does not exist. + + * 2a1. FaculType shows an error message. + + Use case resumes at step 1. + +**Use case: Find module(s)** + +**MSS** + +1. User requests to list modules +2. FaculType shows the list of modules +3. User requests to find module(s) by their attributes +4. FaculType shows a list of modules that fulfills all constraints specified + + Use case ends. + +**Extensions** + +* 2a. The module list is empty. + + Use case ends. + +* 3a. The user's keywords are invalid. + + * 3a1. FaculType shows an error message. + + Use case resumes at step 2. + +
+ +**Use case: Assign a contact to module(s)** + +**MSS** + +1. User requests to list contacts +2. FaculType shows the list of contacts +3. User requests to list modules +4. FaculType shows the list of modules +5. User requests to assign a contact to a module +6. FaculType assigns the contact to the module + + Use case ends. + +**Extensions** + +* 2a. The contact list is empty. + + Use case ends. + +* 4a. The module list is empty. + + Use case ends. + +* 5a. The given contact does not exist. + + * 5a1. FaculType shows an error message. + + Use case resumes at step 4. + +* 5b. The given module does not exist. + + * 5b1. FaculType shows an error message. + + Use case resumes at step 4. + +**Use case: Unassign a contact from module(s)** + +**MSS** + +1. User requests to list contacts +2. FaculType shows the list of contacts +3. User requests to list modules +4. FaculType shows the list of modules +5. User requests to unassign a contact from some modules +6. FaculType updates the instructor list of the specified modules + + Use case ends. + +
+ +**Extensions** + +* 2a. The contact list is empty. + + Use case ends. + +* 4a. The module list is empty. + + Use case ends. + +* 5a. The given contact does not exist. + + * 5a1. FaculType shows an error message. + + Use case resumes at step 4. + +* 5b. Any of the modules specified does not exist. + + * 5b1. FaculType shows an error message. + + Use case resumes at step 4. + +* 5c. The contact is not an instructor for any of the modules. + + * 5c1. FaculType shows an error message. + + Use case resumes at step 4. + +**Use case: Unassign a contact from all modules** + +**MSS** + +1. User requests to list contacts +2. FaculType shows the list of contacts +3. User requests to list modules +4. FaculType shows the list of modules +5. User requests to unassign a contact from all modules +6. FaculType updates the instructor list of the modules + + Use case ends. + +
+ +**Extensions** + +* 2a. The contact list is empty. + + Use case ends. + +* 4a. The module list is empty. + + Use case ends. + +**Use case: Unassign all contacts from all modules** + +**MSS** + +1. User requests to list contacts +2. FaculType shows the list of contacts +3. User requests to list modules +4. FaculType shows the list of modules +5. User requests to unassign all contacts from all modules +6. FaculType updates the instructor list of all modules + + Use case ends. + +**Extensions** + +* 2a. The contact list is empty. + + Use case ends. + +* 4a. The module list is empty. + + Use case ends. + +
+ +### Non-Functional Requirements + +1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed. +2. Should be able to hold up to 1000 contacts 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. Should be usable by a novice who has never used a contact management system/command line application before. +5. Should adhere to the schedule specified in the CS2103 website. +6. Not required to support contacting the faculty members. +7. Not required to handle printing of faculty member/module data. +8. Not required to connect to any backend system/DBMS. +9. Not required to support multiple users on a single device. +10. Not required to support any language other than English. +11. Should be able to work without users having Gradle/JavaFX installed beforehand. +12. Each time a user opens the application, the user should be able to view the latest version of the data (new/updated data should be there and deleted data should no longer exist). + +### Glossary + +* **Mainstream OS**: Windows, Linux, Unix, OS-X +* **Contact**: A member of a faculty +* **Contact attribute**: A piece of information associated to a contact, i.e. name, contact, number, email, department, office. +* **Remark**: A short description of a contact. A remark is optional. +* **Tag**: An optional one-word identifier of a contact. A contact can have multiple tags. +* **Module**: A course held in a college or university. A module can be assigned to a contact. +* **Module attribute**: A piece of information associated to a module, i.e. module code, module name. +* **Module code**: A shorter unique identifier of a module. +* **Module name**: An identifier for a module that is more descriptive than the module code. +* **Instructor** : A contact who instructs a particular module. +* **Assignment**: A module handled by a contact. Assignment links a contact with a module. Once linked, the contact can be considered an instructor. + +-------------------------------------------------------------------------------------------------------------------- + +
+ +## **Appendix: Instructions for manual testing** + +Given below are instructions to test the app manually. + +
:information_source: **Note:** These instructions only provide a starting point for testers to work on; +testers are expected to do more *exploratory* testing. + +
+ +### Launch and shutdown + +1. Initial launch + + 1. Download the jar file and copy into an empty folder + + 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. + +1. Saving window preferences + + 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. + +### Listing all contacts and modules + +1. Test case: `list` + Expected: All contacts and modules are shown in the contact list. + +1. Test case: `list x` + Expected: Both lists remain in their original state. Error details shown in the status message. + +
+ +### Listing all contacts + +1. Test case: `clist` + Expected: All contacts are shown in the contact list. + +1. Test case: `clist x` + Expected: Contact list remain in their original state. Error details shown in the status message. + +### Listing all modules + +1. Test case: `mlist` + Expected: All modules in the active semester are shown in the module list. + +1. Test case: `mlist x` + Expected: Module list remain in their original state. Error details shown in the status message. + +### Switching the active semester + +1. Prerequisites: Active semester is Semester 1. + +1. Test case: `switch`
+ Expected: Active semester switched to Semester 2. Module list view updates to the active semester. + +1. Test case: `switch m/`
+ Expected: Active semester unchanged. Error details shown in the status message. + +1. Other incorrect assign commands to try: `switch switch` + Expected: Similar to previous. + +### Resetting data + +1. Test case: `reset` + Expected: All contacts and modules are deleted from the app. + +1. Test case: `reset x` + Expected: Nothing is changed or deleted. Error details shown in the status message. + +
+ +### Clearing all contacts from the contact list + +1. Prerequisites : List all contacts using the `list` or `clist` command. + +1. Test case : `cclear`
+Expected : Success message saying "All contacts deleted" + +1. Test case : `cclear` on an empty contact list
+Expected : Error message saying "Contact list is already empty". + +### Clearing all modules from the module list + +1. Prerequisites : List all modules using the `list` or `mlist` command. + +1. Test case : `mclear`
+Expected : Success message saying "All modules deleted" + +1. Test case : `mclear` on an empty module list
+Expected : Error message saying "Module list is already empty". + +### Help + +1. Test case: `help` + Expected: A pop-up help window is shown. + +1. Test case: Press the `F1` key + Expected: A pop-up help window is shown. + +1. Test case: `help x` + Expected: No changes. Error details shown in the status message. + +### Exiting the program + +1. Test case: `exit` + Expected: The program exits, all changes are saved. + +1. Test case: `exit x` + Expected: No changes. The application remains running. Error details shown in the status message. + +
+ +### Saving data + +1. Dealing with corrupted data files + + 1. Open the `FaculType.jar` file and do any type of modification on the contact or module list. + 1. Inside the data folder edit the `addressbook.json` file and do any of the following: + 1. Invalid person test cases + - Test case: invalid name
+ Insert any special character into the `name` attribute in `persons`.
+ Example: `Alex Yeoh**`
+ Expected: FaculType will restart with an empty contact and module list. + - Test case: invalid phone number
+ Insert an alphabetical or special characters into the `phone` attribute in `persons`.
+ Example: `987654321abcd`
+ Expected: Similar to previous. + - Test case: invalid email
+ Modify the email to be in an invalid format.
+ Example: `alexgmail`
+ Expected: Similar to previous. + - Test case: invalid office
+ Insert any special character into the `department` attribute in `persons`.
+ Example: `Math??`
+ Expected: Similar to previous. + - Test case: invalid tag
+ Modify the tag to have more than one word.
+ Example: `best friend`
+ Expected: Similar to previous. + 1. Invalid module test cases + - Test case: invalid module code
+ Modify the `moduleCode` to have in `modules` to have more than one word or have a special character.
+ Example: `CS123**`
+ Expected: FaculType will restart with the placeholder contacts and modules, all previous information + will be deleted. + - Test case: invalid module name
+ Insert any special character into the module name.
+ Example: `Programming Meth***`
+ Expected: Similar to previous. + - Test case: invalid instructor
+ Do any of the above invalid person test cases into the `instructors` in any of the module in `modules`.
+ Expected: Similar to previous. + 1. Duplicate contacts
+ Test case: copy and paste any contact in `persons`.
+ Expected: Similar to previous. + 1. Duplicate modules
+ Test case: copy and paste any module in `semOneModules` or `semTwoModules`.
+ Expected: Similar to previous. + 1. Duplicate instructors
+ Test case: copy and paste any instructor in `instructors` in `semOneModules` or `semTwoModules`.
+ Expected: FaculType will restart as usual, with no duplicate instructor displayed in the instructor list. + 1. Non-existent instructor
+ Test case: copy and paste any contact in `persons` into `instructors` in `semOneModules` or `semTwoModules + ` and edit any of its attributes.
+ Expected: FaculType will restart as usual, with the added non-existent instructor in the module's + instructor list (This is a bug). + 1. Invalid JSON format
+ Test case: delete `semOneModules`, `semTwoModules`, `persons` or erase any commas (`,`) or brackets + (`{ }`), or colons (`:`).
+ Expected: FaculType will restart with an empty contact and module list. + +1. Dealing with missing files. + + 1. Test case: delete `config.json`
+ Expected: FaculType will restart as usual. + + 1. Test case: delete `preferences.json`
+ Expected: Previous user preferences such as window size will be deleted and FaculType will restart with the + default GUI settings. + + 1. Test case: delete `data/addressbook.json`
+ Expected: All contact and module information will be deleted and FaculType will restart with the placeholder + contact and module information. + +
+ +### Adding a contact + +1. Adding a contact while all contacts are being shown + + 1. Prerequisites: List all contacts using the `list` or `clist` command. + + 1. Test case: `add n/Alice Liddel p/987654321 e/aliceliddel@example.com d/FASS o/AS5-04-03`
+ Expected: The contact is added as the last index in the contact list. Details of the added contact shown in the + status message. + + 1. Test case: `add n/Alice Liddel p/9876**** e/aliceliddel@example.com d/FASS o/AS5-04-03`
+ Expected: The contact is not added. Error details shown in the status message. + + 1. Test case: `add n/Alice Liddel`
+ Expected: No contact is added. Error details shown in the status message. + + 1. Other incorrect add commands to try: `add`, `add n/Alice * p/8765432 e/aliceliddel@example.com d/FASS o/As5 + -04-03`, `...`
+ Expected: Similar to previous. + +1. Adding a contact while contacts are being filtered + + 1. Prerequisites: Filter contacts by attributes using the `find` command. + + 1. Test cases similar to previous. + Expected: similar to each respective test cases but the contact list is reset to show all contacts. + +1. Adding a contact with identical attributes + + 1. Prerequisites: There exists a contact in the contact list. + Example:
+ `name: Alex Yeoh`
+ `phone: 987654321`
+ `email: alexyeoh@example.com`
+ `department: Computing`
+ `office: COM2-03-04` + + 1. Test case: `add n/Alex Yeoh p/87654321 e/alexyeoh@example.com d/Computing o/COM2-03-04` + Expected: The contact is added as the last index in the list. Details of the added contact shown in the status + message. + + 1. Test case: `add n/Alex Yeoh p/987654321 e/alexyeoh@example.com d/FASS o/AS5-04-03` + Expected: The contact is not added. Error details shown in the status message. + +
+ +### Deleting a contact + +1. Deleting a contact while all contacts are being shown + + 1. Prerequisites: List all contacts using the `list` or `clist` command. Multiple contacts 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. + + 1. Test case: `delete 0`
+ Expected: No contact is deleted. Error details shown in the status message. + + 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
+ Expected: Similar to previous. + +1. Deleting a contact while contacts are being filtered + + 1. Prerequisites: Filter contacts by attributes using the `find` command. Other prerequisites are similar to previous. + + 1. Test cases similar to previous. + +
+ +### Editing a contact + +1. Editing a contact while all contacts are being shown + + 1. Prerequisites: List all contacts using the `list` or `clist` command. Multiple contacts in the list. + + 1. Test case: `edit 1 n/Tony Stark`
+ Expected: First contact's name is changed to `Tony Stark`. Details of the edited contact shown in the status + message. + + 1. Test case: `edit 1 t/`
+ Expected: First contact's tags are deleted. Details of the edited contact shown in the status message. + + 1. Test case: `edit 1 n/**`
+ Expected: No contact is edited. Error details shown in the status message. + + 1. Test case: `edit 0`
+ Expected: No contact is edited. Error details shown in the status message. + + 1. Other incorrect edit commands to try: `edit`, `edit x`, `edit p/98765abcd`, `...` (where x is larger than the + list size)
+ Expected: Similar to previous. + +1. Editing a contact while contacts are being filtered + + 1. Prerequisites: Filter contacts by attributes using the `find` command. Other prerequisites are similar to previous. + + 1. Test cases similar to previous. + Expected: Similar to each respective test cases, but the contact list is reset to show all contacts. + +
+ +### Adding or editing a remark + +1. Adding or editing a remark while all contacts ae being shown + + 1. Prerequisites: List all contacts using the `list` or `clist` command. + + 1. Test case: `remark 1 r/Wears glasses`
+ Expected: The first contact in the list is edited to have a remark of "Wears glasses". Details of the added + contact shown in the status message. + + 1. Test case: `remark 1 r/`
+ Expected: The first contact's remark is removed. Details of the edited contact shown in the status message. + + 1. Test case: `remark 0 r/`
+ Expected: No contact edited. Error details shown in the status message. + + 1. Other incorrect remark commands to try: `remark`, `remark 1`, `remark x r/`, `...` (where x is larger than + the list size)
+ Expected: Similar to previous. + +1. Adding or editing a remark while contacts are filtered. + + 1. Prerequisites: Filter contacts by attributes using the `find` command. + + 1. Test cases similar to previous. + Expected: Similar to each respective test cases. + +### Finding contacts by attributes + +1. Prerequisites: Multiple contacts in the list. + +1. Test case: `find n/Alice d/Math`
+ Expected: All contacts that has "Alice" in their name, and "Math" in their department are shown. + +1. Test case: `find n/`
+ Expected: No contacts filtered. Error details shown in the status message. + +1. Other incorrect find commands to try: `find p/abcdef`, `find`, `find Alice`, `...` + Expected: Similar to previous. + +
+ +### Adding a module + +1. Adding a module while all modules are being shown + + 1. Prerequisites: List all modules using the `list` or `mlist` command. + + 1. Test case: `addmod m/CS2103 n/Software Engineering`
+ Expected: The module is added as the last index in the module list of the active semester. Details of the added + module shown in the status message. + + 1. Test case: `addmod m/CS2103`
+ Expected: The module is added as the last index in the module list. Details of the added module shown in the + status message. + + 1. Other incorrect add module commands to try: `addmod`, `addmod m/CS** n/Programming`, `...`
+ Expected: Similar to previous. + +1. Adding a module while modules are being filtered + + 1. Prerequisites: Filter modules by attributes using the `findmod` command. + + 1. Test cases similar to previous. + Expected: Similar to each respective test cases, but the module list is reset to show all modules. + +
+ +### Deleting a module + +1. Deleting a module while all modules are being shown + + 1. Prerequisites: List all modules using the `list` or `mlist` command. + Delete a module from the module list using the `delmod` command. There are only 3 modules with module codes `CS2103`, `CS2100`, `CS1010S` in FaculType. + + 1. Test case: `delmod m/CS2103` + Expected: Module with module code `CS2103` is deleted from the module list. + + 1. Test case: `delmod m/CS1101S` + Expected: No module is deleted from the module list since `CS1101S` is not a module that exists in the module list. Error details shown in the status message. + + 1. Test case: `delmod m/CS2103 m/CS2100` + Expected: No module is deleted from the module list because `delmod` does not allow for multiple deletions. Error details shown in the status message. + +1. Deleting a module while modules are being filtered + + 1. Prerequisites: Filter modules using the `findmod` command. Other prerequisites are similar to previous. + + 1. Test cases similar to previous. + +### Finding modules + +1. Prerequisites: Multiple modules in the list. + +1. Test case: `findmod m/CS1010 n/Programming`
+ Expected: All contacts that has "CS1010" in their module code, *and* "Programming" in their module name are shown. + +1. Test case: `findmod m/`
+ Expected: No modules filtered. Error details shown in the status message. + +1. Other incorrect find module commands to try: `findmod m/CS**`, `findmod`, `findmod Alice`, `...` + Expected: Similar to previous. + +
+ +### Assigning a contact to one or more modules + +1. Assigning a contact while all contacts are being shown + + 1. Prerequisites: List all contacts and modules in the active semester using the `list` command. There are only 3 modules in the active semester with module codes `CS2103`, `CS2100`, `CS1010S`. + Contact on index `1` is not an instructor of any module, while contact on index `2` is an instructor of modules with module codes `CS2103` and `CS2100`. + + 1. Test case : `assign 1 m/CS1010S`
+ Expected: First contact is assigned to the CS1010S module. Name of contact shown in module card. + + 1. Test case : `assign 1 m/CS2103 m/CS2100`
+ Expected: First contact is assigned to both CS2103 and CS2100 modules. Name of contact shown in module cards. + + 1. Test case: `assign 2 m/CS2103 m/CS1010S`
+ Expected: No contacts assigned to any modules. Error details shown in the status message. + + 1. Test case: `assign 0 m/CS2103 m/CS2100`
+ Expected: No contacts assigned to any modules. Error details shown in the status message. + + 1. Test case: `assign 1 m/CS3230`
+ Expected: No contacts assigned to any modules. Error details shown in the status message. + + 1. Other incorrect assign commands to try: `assign m/cs2013`, `assign`, `assign x m/cs2103`, `...` (where x is larger than the list size)
+ Expected: Similar to previous. + +1. Assigning a contact while contacts are being filtered + + 1. Prerequisites: Filter contacts by attributes using the `find` command and list all modules using the `mlist` command. Other prerequisites are similar to previous. + + 1. Test cases similar to previous. + +
+ +### Unassigning a contact from one or more modules + +1. Unassigning a contact while all contacts are being shown + + 1. Prerequisites : List all contacts and modules using the `list` command. There are only 3 modules in the active semester with module codes `CS2103`, `CS2100`, `CS1010S`. + Contact on index `1` is an instructor of module with module code `CS2103` and `CS2100`, while contact on index `2` is an instructor of module with module code `CS2100` and `CS1010S`. + + 1. Test case : `unassign 1 m/CS2103 m/CS2100`
+ Expected : First contact is unassigned from both CS2103 and CS2100 modules. First contact is no longer an instructor of CS2103 nor CS2100 module. Name of contact removed from module cards. + + 1. Test case : `unassign 2 m/CS2103 m/CS2100`
+ Expected : No contact is unassigned from any modules because instructor on index `2` is not an instructor of module `CS2103`. + + 1. Test case : `unassign 0 m/CS1010S`
+ Expected : No contact is unassigned from any modules. Error details shown in the status message. + + 1. Test case : `unassign 1 m/CS3230`
+ Expected : No contact is unassigned from any modules. Error details shown in the status message. + + 1. Other incorrect unassign commands to try : `unassign`, `unassign x m/y` (where x is larger that the list size or is not an instructor of module y), `unassign a m/b` (where b does not exist in FaculType)
+ Expected : Similar to previous. + +1. Unassigning a contact while contacts are being filtered + + 1. Prerequisites: Filter contacts by attributes using the `find` command and list all modules using the `mlist` command. Other prerequisites are similar to previous. + + 1. Test cases similar to previous. + +
+ +### Unassigning all contacts + +1. Prerequisites: There exists multiple modules and contacts in FaculType. + +1. Test case: `unassignall` + Expected: All contacts are unassigned from all modules. + +1. Test case: `unassignall abcd` + Expected: No contact is unassigned. Error details shown in the status message. + +-------------------------------------------------------------------------------------------------------------------- + +
+ +## **Appendix: Effort** + +### Changes from AB3 + +The team modified some features of AB3. A person originally had a name, phone number, email, address, and tags. In FaculType, a person no longer has an address, but has a department, office, and remark. +These additional attributes required the team to change the `Person` class inside of the `Model` component, which in turn caused the other classes using `Person` to be updated as well. +The `add` and `edit` commands are modified to support these new attributes. The team has also modified the `find` command. In AB3, the find command can only `find` based on a person's name. +In FaculType, the `find` command can find persons based on any attribute they have (e.g. name, phone number, office, etc.). + +The team added a new entity `Module` for module management. Modules have their own list, so now FaculType has two types of lists for persons and modules, compared to only one list for persons in AB3. +Since there are now two types of lists, the team added additional commands to complement the `clear` and `list` commands. `clear` is renamed to reset and would reset all data in FaculType. +`cclear` and `mclear` as well as `clist` and `mlist` are the respective equivalents of AB3's `clear` and `list` for the person list and the module list. +Modules can be added and deleted with `addmod` and `delmod` The team implemented the `findmod` command which is +similar to the `find` command but for modules, and it allows searching by module attributes. + +The team added `assign`, `unassign` and `unassignall` to manage the person-module relationship. +A second module list was required to implement the two-semester system. The team added `switch` to toggle between these two module lists. +All the commands above related to module management and assignment are all new features that do not exist in AB3. + +The team modified the GUI to accommodate these changes, including the color scheme and overall structure. The team designed a logo for FaculType to become the program's icon. + +The project is harder to develop than AB3 because FaculType has two entity types (compared to one in AB3) and the module entity type has to have two separate lists. + +
+ +### Difficulties and Challenges + +As most of the developer team had never worked on a brown-field project before, working on the project seemed like a monumental task at first, even with the tutorials provided. +Familiarizing oneself to the large codebase took up quite some time, and the team ended up learning the codebase while working on new features. A small change in one part of the codebase sometimes required a lot of changes in other areas of the codebase. +The fact that the software architecture was somewhat similar to that of the individual project (iP) made it slightly easier to understand. + +JavaFX also proved to be difficult to master. A lot of GUI bugs occured and fixing them was very challenging. -1. Dealing with missing/corrupted data files +### Effort Required - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ +The team had to meet up at least once a week to discuss the current week's deliverables and assign each team member to certain tasks. +Some additional meetings were held for intensive manual testing of the application. +A working product had to be done by the end of each week, even if the current iteration was not finished yet. This way it was easier to identify bugs and feature changes that had to be made. -1. _{ more test cases …​ }_ +Each team member had to be ready to review PRs every day of the week and continuous communication through a chat group was necessary to ensure everybody was on the same page. +Documentation files had to be updated regularly to ensure every feature listed in the files is accurate as of the current feature's implementation. diff --git a/docs/UserGuide.md b/docs/UserGuide.md index b91c3bab04d..03a32275f15 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -3,41 +3,41 @@ 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. +FaculType is a **desktop app** for managing **faculty members and their modules**, optimized for use via a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). -* Table of Contents -{:toc} +-------------------------------------------------------------------------------------------------------------------- + +## Table of Contents +* [Quick Start](#quick-start) +* [Features](#features) + * [General Features](#general-features) + * [Contact Management](#contact-management) + * [Module Management](#module-management) + * [Instructor Assignment](#instructor-assignment) + * [Upcoming Features](#upcoming-features) +* [Current Bugs](#current-bugs) +* [FAQ](#faq) +* [Command Summary](#command-summary) -------------------------------------------------------------------------------------------------------------------- -## Quick start +## 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). +1. Download the latest `FaculType.jar` from [here](https://github.com/AY2021S1-CS2103-T14-1/tp/releases). -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +1. Copy the file to the folder you want to use as the _home folder_ for FaculType. 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) + ![Ui](images/UiInit.png) 1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
- Some example commands you can try: - - * **`list`** : Lists all contacts. - - * **`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. - - * **`delete`**`3` : Deletes the 3rd contact shown in the current list. - - * **`clear`** : Deletes all contacts. - - * **`exit`** : Exits the app. 1. Refer to the [Features](#features) below for details of each command. -------------------------------------------------------------------------------------------------------------------- - +
## Features
@@ -58,121 +58,335 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo
-### Viewing help : `help` +### General Features + +#### Listing all contacts and modules: `list` + +Shows a list of all contacts in FaculType and all modules in the active semester. + +Format: `list` + +#### Listing all contacts : `clist` + +Shows a list of all contacts in FaculType. + +Format: `clist` + +#### Listing all modules : `mlist` + +Shows a list of all modules in the active semester. + +Format: `mlist` + +
+#### Switching the active semester : `switch` + +Switches the active semester from Semester 1 to Semester 2 and vice versa. + +Format: `switch` + +#### Reseting FaculType : `reset` + +Resets FaculType to its initial state by clearing all entries of persons and modules. + +Format: `reset` + +#### Clearing all contacts : `cclear` + +Clears all entries of contacts in FaculType. -Shows a message explaning how to access the help page. +Format : `cclear` -![help message](images/helpMessage.png) +#### Clearing all modules : `mclear` + +Clears all entries of modules in the active semester. + +Format : `mclear` + +#### Viewing help : `help` + +Shows a message explaining how to access the help page. Format: `help` +#### Exiting the program : `exit` -### Adding a person: `add` +Exits the program. + +Format: `exit` -Adds a person to the address book. +#### Saving the data -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +FaculType data is saved in the hard disk automatically after any command that changes the data. There is no need to save manually. + +
+### Contact Management + +#### Adding a contact: `add` + +Adds a contact to FaculType. + +Format: `add n/NAME p/PHONE_NUMBER e/EMAIL d/DEPARTMENT o/OFFICE [t/TAG]…​ +​`
:bulb: **Tip:** -A person can have any number of tags (including 0) +A contact can have any number of tags (including 0)
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` +* `add n/Janson Garrick p/98765432 e/jansongarrick@example.com d/Computer Science o/B01-A3` +* `add n/Amanda Holt p/98765431 e/amanda123@example.com d/Data Science o/COM1-02-03 t/lecturer t/friend` -### Listing all persons : `list` +#### Deleting a contact : `delete` -Shows a list of all persons in the address book. +Deletes the specified contact from FaculType. -Format: `list` +Format: `delete INDEX` -### Editing a person : `edit` +* Deletes the contact at the specified `INDEX`. +* The index refers to the index number shown in the displayed contact list. +* The index **must be a positive integer** 1, 2, 3, …​ + +Examples: +* `clist` followed by `delete 2` deletes the 2nd contact in FaculType. +* `find n/Janson` followed by `delete 1` deletes the 1st contact in the results of the `find` command. + +
+#### Editing a contact : `edit` -Edits an existing person in the address book. +Edits an existing contact in FaculType. -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [d/DEPARTMENT] [o/OFFICE] [t/TAG]…​` -* 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, …​ +* Edits the contact at the specified `INDEX`. The index refers to the index number shown in the displayed contact 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 +* When editing tags, the existing tags of the contact will be removed i.e. adding of tags is not cumulative. +* You can remove all the contact’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. +* `edit 1 d/Computing o/COM2-01-02` edits the department and office of the 1st contact to be `Computing` and `COM2-01-02` respectively. +* `edit 2 n/Brenda Chan t/` edits the name of the 2nd contact to be `Brenda Chan` and clears all existing tags. -### Locating persons by name: `find` +#### Adding or updating a remark : `remark` -Finds persons whose names contain any of the given keywords. +Adds or updates the remark of an existing contact in FaculType. -Format: `find KEYWORD [MORE_KEYWORDS]` +Format: `remark INDEX r/[REMARK]` -* 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` +* Adds a remark to the contact at the specified `INDEX`. The index refers to the index number shown in the displayed contact list. The index **must be a positive integer** 1, 2, 3, …​ +* You can remove the contact’s remark by typing `r/` without specifying any remark after it. Examples: -* `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) +* `remark 1 r/Wears red glasses` adds the remark “Wears red glasses” to the 1st contact in the list. +* `remark 2 r/` erases the remark of the 2nd contact in the list. -### Deleting a person : `delete` +
+#### Locating contacts by attributes: `find` -Deletes the specified person from the address book. +Finds all contacts that match the given fields. -Format: `delete INDEX` +Format: `find [n/NAME] [p/PHONE] [e/EMAIL] [d/DEPARTMENT] [o/OFFICE] [r/REMARK] [t/TAG]` -* 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, …​ +* The search is case-insensitive. e.g. `chris` will match `Chris`. +* The order of the keywords does not matter. e.g. `Chris Evans` will match `Evans Chris`. +* Partial words will be matched e.g. `Chri Evan` will match `Chris Evans`. +* Results must contain every keyword e.g. `Chris Evans` will not match `Chris Pratt`. +* At least one of the optional fields must be provided. 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. +* `find n/John` returns all contacts with names containing `John`. +* `find n/alex yeoh` returns all contacts with names containing `alex` and `yeoh`. +* `find n/victor tan d/computing` returns all contacts with names containing `victor` and `tan` and with departments +containing `computing`. -### Clearing all entries : `clear` +### Module Management -Clears all entries from the address book. +#### Adding a module: `addmod` -Format: `clear` +Adds a new module to the active semester. -### Exiting the program : `exit` +Format : `addmod m/MODULE_CODE n/MODULE_NAME` -Exits the program. +* Adds the `MODULE_CODE` specified to the active semester. The `MODULE_CODE` must not exist in the active semester in the first place. -Format: `exit` +Examples: +* `addmod m/CS50 n/Introduction to Computer Science` adds a module named `Introduction to Computer Science` with code `CS50` to the active semester. +* `addmod m/CS2102 n/Database Systems` adds a module named `Database Systems` with code `CS2102` to the active semester. -### Saving the data +
+#### Deleting a module: `delmod` -AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +Deletes a module from the active semester. -### Archiving data files `[coming in v2.0]` +Format: `delmod m/MODULE_CODE` + +* Deletes the `MODULE_CODE` specified from the active semester. The `MODULE_CODE` **must exist** in the active semester in the first place. +The command takes in **only one** `MODULE_CODE` parameter and does not allow for the deletion of multiple modules. + +Examples: +* `delmod m/CS50` deletes the existing module with code `CS50` from the active semester. +* `delmod m/CS2102` deletes the existing module with code `CS2102` from the active semester. -_{explain the feature here}_ +#### Finding modules : `findmod` + +Finds all modules in the active semester that match the given fields. + +Format : `findmod [m/MODULE_CODE] [n/MODULE_NAME] [i/INSTRUCTOR_NAME]` + +* `MODULE_CODE` parameter **does not** allow for multiple keywords. e.g. `cs1 cs2` would not be allowed. +* Partial words will be matched for all parameters. e.g. `data` will match `Database Systems`. +* The search is case-insensitive for all parameters. e.g. `cs50` will match `CS50`. +* The order of the keywords do not matter. e.g. `Statistics and Probability` will match `Probability and Statistics`. +* At least one of the optional fields must be provided. + +Examples : + +* `findmod m/cs210` returns all modules with codes containing `CS210`. +* `findmod n/programming` returns all modules with names containing `programming`. +* `findmod n/software methodology` returns all the modules with names containing `software` **and** `methodology`. +* `findmod m/CS2 n/security i/Alex` returns all modules with codes containing `CS2`, names containing `programming`, +**and** instructors with names containing `Alex`. +* `findmod m/CS2 n/Software Programming i/Damith` returns all modules with codes containing `CS2` **and** +names containing `Software` **and** `Programming` **and** instructors with names `Damith`. + +
+### Instructor Assignment + +#### Assigning an instructor to modules : `assign` + +Assigns a contact to one or more modules in the active semester. The contact will be an instructor in those modules. + +Format: `assign INDEX m/MODULE_CODE [m/MODULE_CODE]…​` + +* Assigns the contact at the specified `INDEX` to every `MODULE_CODE` specified. All `MODULE_CODE` **must be unique** and **must exist** in the active semester in the first place. + +Examples : +* `assign 1 m/CS3233` Assigns the contact at index 1 to the existing module with code `CS3233`. +* `assign 2 m/CS2030S` Assigns the contact at index 2 to the existing module with code `CS2030S`. +* `assign 3 m/CS2100 m/CS2106` Assigns the contact at index 3 to the existing modules with codes `CS2100` and `CS2106`. + +
+Before assignment: + +![BeforeAssignment](images/BeforeAssignment.png) + +
+After assignment: + +![AfterAssignment](images/AfterAssignment.png) + +
+#### Unassigning an instructor from modules : `unassign` + +Unassigns a contact from one or more modules in the active semester. The contact will no longer be an instructor in those modules. + +Format: `unassign INDEX m/[MODULE_CODE] [m/MODULE_CODE]…​` + +* Unassigns the contact at the specified `INDEX` from every `MODULE_CODE` specified. All `MODULE_CODE` **must be unique** and the contact **must be assigned** to all `MODULE_CODE` in the active semester in the first place. +* You can unassign the contact from all modules in the active semester by typing `m/` + without specifying any module codes after it. + +Examples : +* `unassign 1 m/CS3233` Unassigns the contact at index 1 from the existing module with code `CS3233`. +* `unassign 3 m/CS2100 m/CS2106` Unassigns the contact at from 3 to the existing modules with codes `CS2100` and `CS2106`. +* `unassign 2 m/` Unassigns the contact at index 2 from all modules in the active semester. + +#### Clearing all assignments : `unassignall` + +Unassigns all contacts from all modules in the active semester. + +Format: `unassignall` + +### Upcoming Features + +The following features are not done yet and will be implemented in the next update. + +* Order filter results of contacts and modules by how much they match the specified parameters (Coming soon). +* Differentiate instructor tags in module cards for instructors with the same name (Coming soon). -------------------------------------------------------------------------------------------------------------------- +
+## Current Bugs + +The following bugs still occur in the current version of FaculType and will be fixed in the next update. Using features in the following ways should be avoided. + +* Editing a contact's identifying attributes (name, phone, email) to be the same as another contact's causes problems due to the detection of duplicate contacts. +This bug is inherited from the parent project AddressBook Level 3. +* A tag in a contact card and an instructor tag in a module card may be cut off if the text inside is too long or if there are too many tags. +This bug is inherited from the parent project AddressBook Level 3. + +Example of the tag bug: + +![TagBug](images/TagBug.png) + +
+ +* No validation of instructor existence for modules. Adding a non-existent instructor by editing the +`addressbook.json` file will not throw any error. + +Example of the instructor bug: + +![JSONFileComposite](images/JSONFileComposite.png) + + +![InstructorBug](images/InstructorBug.png) + +-------------------------------------------------------------------------------------------------------------------- + +
## FAQ **Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder. +**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 FaculType home folder. + +**Q**: What are instructors?
+**A**: Instructors are contacts that have been assigned to one or more modules. + +**Q**: Can I directly assign a contact to a module in the non-active semester?
+**A**: No. Use `switch` to switch the active semester first and then use `assign`. + +**Q**: What happens if I accidentally clear all my data using `reset`?
+**A**: FaculType currently does not support an `undo` operation so it is not possible to retrieve deleted data. Please ensure that you wish to delete all your data before using `reset`. + +**Q**: When and why does FaculType use sample data?
+**A**: FaculType uses sample data when the application is first launched. The purpose of providing sample data is to let users experiment with the data while getting familiar with FaculType. If you wish to insert your own data, you can use `reset`. -------------------------------------------------------------------------------------------------------------------- -## Command summary +## Command Summary + +Action | Format, Examples +--------|------------------ +**List all contacts and modules** | `list` +**List all contacts** | `clist` +**List all modules** | `mlist` +**Switch active semester** | `switch` + +
+ +Action | Format, Examples +--------|------------------ +**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL d/DEPARTMENT o/OFFICE [t/TAG]…​`
e.g. `add n/Betsy Crowe p/98765431 e/betsycrowe@example.com d/Data Science o/COM1-02-03 t/lecturer t/friend` +**Delete** | `delete INDEX`
e.g. `delete 3` +**Edit** | `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [d/DEPARTMENT] [o/OFFICE] [t/TAG]…​`
e.g. `edit 1 d/Information Systems o/COM2-01-02` +**Remark** | `remark INDEX r/[REMARK]`
e.g. `remark 1 r/Wears red glasses` +**Find** | `find [n/NAME] [p/PHONE] [e/EMAIL] [d/DEPARTMENT] [o/OFFICE] [r/REMARK] [t/TAG]`
e.g. `find n/Victor Tan d/Math` +**Add modules** | `addmod m/MODULE_CODE n/MODULE_NAME`
e.g. `addmod m/CS2103 n/Software Engineering` +**Delete modules** | `delmod m/MODULE_CODE`
e.g. `delmod m/CS2103` +**Find modules** | `findmod [m/MODULE_CODE] [n/MODULE_NAME] [i/INSTRUCTOR_NAME]`
e.g. `findmod m/CS2` +**Assign a contact** | `assign INDEX m/MODULE_CODE [m/MODULE_CODE]…​`
e.g. `assign 3 m/CS2100 m/CS2106` +**Unassign a contact** | `unassign INDEX m/[MODULE_CODE] [m/MODULE_CODE]…​`
e.g. `unassign 3 m/CS2100 m/CS2106` +**Unassign all contacts** | `unassignall` + +
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` +**Clear all data** | `reset` +**Clear all contacts** | `cclear` +**Clear all modules** | `mclear` **Help** | `help` +**Exit** | `exit` diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..8f22e1ee402 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "FaculType" theme: minima header_pages: @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "AY2021S1-CS2103-T14-1/tp" github_icon: "images/github-icon.png" plugins: diff --git a/docs/diagrams/AssignActivityDiagram.puml b/docs/diagrams/AssignActivityDiagram.puml new file mode 100644 index 00000000000..7e259a2cc0d --- /dev/null +++ b/docs/diagrams/AssignActivityDiagram.puml @@ -0,0 +1,22 @@ +@startuml +start +:User executes assign command; + +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +if () then ([at least one module is specified + and all specified modules exist]) + if () then ([contact is not assigned to + any specified modules]) + :Assign specified contact to specified modules; + :Update GUI to show changes; + + else([else]) + : Show error message; + endif +else ([else]) + : Show error message; +endif +stop +@enduml diff --git a/docs/diagrams/AssignSequenceDiagram.puml b/docs/diagrams/AssignSequenceDiagram.puml new file mode 100644 index 00000000000..394f6d5fba0 --- /dev/null +++ b/docs/diagrams/AssignSequenceDiagram.puml @@ -0,0 +1,72 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant "parser:AssignCommandParser" as AssignCommandParser LOGIC_COLOR +participant "a:AssignCommand" as AssignCommand LOGIC_COLOR +participant "result:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("assign 1 m/CS2103") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("assign 1 m/CS2103") +activate AddressBookParser + +create AssignCommandParser +AddressBookParser -> AssignCommandParser +activate AssignCommandParser + +AssignCommandParser --> AddressBookParser +deactivate AssignCommandParser + +AddressBookParser -> AssignCommandParser : parse(" 1 m/CS2103") +activate AssignCommandParser + +create AssignCommand + +AssignCommandParser -> AssignCommand +activate AssignCommand + +AssignCommand --> AssignCommandParser +deactivate AssignCommand + +AssignCommandParser --> AddressBookParser : a +deactivate AssignCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +AssignCommandParser -[hidden]-> AddressBookParser +destroy AssignCommandParser + +AddressBookParser --> LogicManager : a +deactivate AddressBookParser + +LogicManager -> AssignCommand : execute() +activate AssignCommand + +AssignCommand -> Model : assignInstructor(1, CS2103) + +activate Model + +Model --> AssignCommand + +deactivate Model + +create CommandResult +AssignCommand -> CommandResult +activate CommandResult + +CommandResult --> AssignCommand +deactivate CommandResult + +AssignCommand --> LogicManager : result +deactivate AssignCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/CclearActivityDiagram.puml b/docs/diagrams/CclearActivityDiagram.puml new file mode 100644 index 00000000000..7ac4011156d --- /dev/null +++ b/docs/diagrams/CclearActivityDiagram.puml @@ -0,0 +1,15 @@ +@startuml +start +:User executes cclear command; + +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +if () then ([contact list is not empty]) + :Clear contacts from the contact list; + :Update GUI to show empty contact list; +else ([else]) + :Show error message; +endif +stop +@enduml diff --git a/docs/diagrams/CclearSequenceDiagram.puml b/docs/diagrams/CclearSequenceDiagram.puml new file mode 100644 index 00000000000..24727caa8a5 --- /dev/null +++ b/docs/diagrams/CclearSequenceDiagram.puml @@ -0,0 +1,72 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant "parser:OneWordCommandParser" as OneWordCommandParser LOGIC_COLOR +participant "c:CclearCommand" as CclearCommand LOGIC_COLOR +participant "result:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("cclear") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("cclear") +activate AddressBookParser + +create OneWordCommandParser +AddressBookParser -> OneWordCommandParser +activate OneWordCommandParser + +OneWordCommandParser --> AddressBookParser +deactivate OneWordCommandParser + +AddressBookParser -> OneWordCommandParser : parse("") +activate OneWordCommandParser + +create CclearCommand + +OneWordCommandParser -> CclearCommand +activate CclearCommand + +CclearCommand --> OneWordCommandParser +deactivate CclearCommand + +OneWordCommandParser --> AddressBookParser : c +deactivate OneWordCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +OneWordCommandParser -[hidden]-> AddressBookParser +destroy OneWordCommandParser + +AddressBookParser --> LogicManager : c +deactivate AddressBookParser + +LogicManager -> CclearCommand : execute() +activate CclearCommand + +CclearCommand -> Model : clearContacts() + +activate Model + +Model --> CclearCommand + +deactivate Model + +create CommandResult +CclearCommand -> CommandResult +activate CommandResult + +CommandResult --> CclearCommand +deactivate CommandResult + +CclearCommand --> LogicManager : result +deactivate CclearCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 1dc2311b245..be3a9acf9f9 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -6,7 +6,7 @@ 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 ":CommandResult" as CommandResult LOGIC_COLOR +participant "result:CommandResult" as CommandResult LOGIC_COLOR end box box Model MODEL_COLOR_T1 @@ -33,7 +33,7 @@ create DeleteCommand DeleteCommandParser -> DeleteCommand activate DeleteCommand -DeleteCommand --> DeleteCommandParser : d +DeleteCommand --> DeleteCommandParser deactivate DeleteCommand DeleteCommandParser --> AddressBookParser : d diff --git a/docs/diagrams/DelmodActivityDiagram.puml b/docs/diagrams/DelmodActivityDiagram.puml new file mode 100644 index 00000000000..c3d16ef1e39 --- /dev/null +++ b/docs/diagrams/DelmodActivityDiagram.puml @@ -0,0 +1,15 @@ +@startuml +start +:User executes delmod command; + +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +if () then ([module exists]) + :Delete specified module; + :Update GUI to show changes; +else ([else]) + :Show error message; +endif +stop +@enduml diff --git a/docs/diagrams/DelmodSequenceDiagram.puml b/docs/diagrams/DelmodSequenceDiagram.puml new file mode 100644 index 00000000000..47cbb5252fb --- /dev/null +++ b/docs/diagrams/DelmodSequenceDiagram.puml @@ -0,0 +1,72 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant "parser:DelmodCommandParser" as DelmodCommandParser LOGIC_COLOR +participant "d:DelmodCommand" as DelmodCommand LOGIC_COLOR +participant "result:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("delmod m/CS2103") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("delmod m/CS2103") +activate AddressBookParser + +create DelmodCommandParser +AddressBookParser -> DelmodCommandParser +activate DelmodCommandParser + +DelmodCommandParser --> AddressBookParser +deactivate DelmodCommandParser + +AddressBookParser -> DelmodCommandParser : parse(" m/CS2103") +activate DelmodCommandParser + +create DelmodCommand + +DelmodCommandParser -> DelmodCommand +activate DelmodCommand + +DelmodCommand --> DelmodCommandParser +deactivate DelmodCommand + +DelmodCommandParser --> AddressBookParser : d +deactivate DelmodCommandParser +DelmodCommandParser -[hidden]-> AddressBookParser +destroy DelmodCommandParser + + +AddressBookParser --> LogicManager : d +deactivate AddressBookParser + +LogicManager -> DelmodCommand : execute() +activate DelmodCommand + +DelmodCommand -> Model : deleteModule(CS2103) + +activate Model + +Model --> DelmodCommand + +deactivate Model + +create CommandResult +DelmodCommand -> CommandResult +activate CommandResult + +CommandResult --> DelmodCommand +deactivate CommandResult + +DelmodCommand --> LogicManager : result +deactivate DelmodCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/FindActivityDiagram.puml b/docs/diagrams/FindActivityDiagram.puml new file mode 100644 index 00000000000..beab4825c71 --- /dev/null +++ b/docs/diagrams/FindActivityDiagram.puml @@ -0,0 +1,18 @@ +@startuml +start +:User executes find command; + +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +if () then ([any of the keywords violate + the given restrictions] +) +:Show error message; +else ([else] +) + :Filters contact list according to keywords; + :Update GUI to show filtered contact list; +endif +stop +@enduml diff --git a/docs/diagrams/FindSequenceDiagram.puml b/docs/diagrams/FindSequenceDiagram.puml new file mode 100644 index 00000000000..2dc7f4fdb89 --- /dev/null +++ b/docs/diagrams/FindSequenceDiagram.puml @@ -0,0 +1,72 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":FindCommandParser" as FindCommandParser LOGIC_COLOR +participant "f:FindCommand" as FindCommand LOGIC_COLOR +participant "result:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("find n/Alice d/Math") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("find n/Alice d/Math") +activate AddressBookParser + +create FindCommandParser +AddressBookParser -> FindCommandParser +activate FindCommandParser + +FindCommandParser --> AddressBookParser +deactivate FindCommandParser + +AddressBookParser -> FindCommandParser : parse("n/Alice d/Math") +activate FindCommandParser + +create FindCommand +FindCommandParser -> FindCommand : {n -> hasAlice(n), d -> hasMath(d)} +activate FindCommand +FindCommand --> FindCommandParser +deactivate FindCommand + +FindCommandParser --> AddressBookParser : f +deactivate FindCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +FindCommandParser -[hidden]-> AddressBookParser +destroy FindCommandParser + +AddressBookParser --> LogicManager : f +deactivate AddressBookParser + +LogicManager -> FindCommand : execute() +activate FindCommand +FindCommand -> FindCommand : composePredicates() +activate FindCommand +FindCommand --> FindCommand : p -> (hasAlice(p) && hasMath(p)) +deactivate FindCommand + +FindCommand -> Model : updateFilteredPersonList(p -> (hasAlice(p) && hasMath(p))) +activate Model + +Model --> FindCommand +deactivate Model + +create CommandResult +FindCommand -> CommandResult +activate CommandResult + +CommandResult --> FindCommand +deactivate CommandResult + +FindCommand --> LogicManager : result +deactivate FindCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/FindmodActivityDiagram.puml b/docs/diagrams/FindmodActivityDiagram.puml new file mode 100644 index 00000000000..959a6a65e40 --- /dev/null +++ b/docs/diagrams/FindmodActivityDiagram.puml @@ -0,0 +1,18 @@ +@startuml +start +:User executes findmod command; + +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +if () then ([any of the keywords violate + the given restrictions] +) +:Show error message; +else ([else] +) + :Filters module list according to keywords; + :Update GUI to show filtered module list; +endif +stop +@enduml diff --git a/docs/diagrams/FindmodSequenceDiagram.puml b/docs/diagrams/FindmodSequenceDiagram.puml new file mode 100644 index 00000000000..d7825675e45 --- /dev/null +++ b/docs/diagrams/FindmodSequenceDiagram.puml @@ -0,0 +1,71 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":FindModCommandParser" as FindModCommandParser LOGIC_COLOR +participant "f:FindModCommand" as FindModCommand LOGIC_COLOR +participant "result:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("findmod m/CS2103 n/Software") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("findmod m/CS2103 n/Software") +activate AddressBookParser + +create FindModCommandParser +AddressBookParser -> FindModCommandParser +activate FindModCommandParser + +FindModCommandParser --> AddressBookParser +deactivate FindModCommandParser + +AddressBookParser -> FindModCommandParser : parse("m/CS2103 n/Software") +activate FindModCommandParser + +create FindModCommand +FindModCommandParser -> FindModCommand : {m -> hasCS2103(m), n -> hasSoftware(n)} +activate FindModCommand +FindModCommand --> FindModCommandParser +deactivate FindModCommand + +FindModCommandParser --> AddressBookParser : f +deactivate FindModCommandParser +FindModCommandParser -[hidden]-> AddressBookParser +destroy FindModCommandParser + +AddressBookParser --> LogicManager : f +deactivate AddressBookParser + +LogicManager -> FindModCommand : execute() +activate FindModCommand +FindModCommand -> FindModCommand : getComposedPredicate() +activate FindModCommand +FindModCommand --> FindModCommand : p -> (hasCS2103(p) && hasSoftware(p)) +deactivate FindModCommand + +FindModCommand -> Model : updateFilteredModuleList(p -> (hasCS2103(p) && hasSoftware(p))) +activate Model + +Model --> FindModCommand +deactivate Model + +create CommandResult +FindModCommand -> CommandResult +activate CommandResult + +CommandResult --> FindModCommand +deactivate CommandResult + +FindModCommand --> LogicManager : result +deactivate FindModCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/MclearActivityDiagram.puml b/docs/diagrams/MclearActivityDiagram.puml new file mode 100644 index 00000000000..df6dc450480 --- /dev/null +++ b/docs/diagrams/MclearActivityDiagram.puml @@ -0,0 +1,15 @@ +@startuml +start +:User executes mclear command; + +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +if () then ([active semester module list is not empty]) + :Clear contacts from the active semester module list; + :Update GUI to show empty active semester module list; +else ([else]) + :Show error message; +endif +stop +@enduml diff --git a/docs/diagrams/MclearSequenceDiagram.puml b/docs/diagrams/MclearSequenceDiagram.puml new file mode 100644 index 00000000000..a62af32d545 --- /dev/null +++ b/docs/diagrams/MclearSequenceDiagram.puml @@ -0,0 +1,72 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant "parser:OneWordCommandParser" as OneWordCommandParser LOGIC_COLOR +participant "m:MclearCommand" as MclearCommand LOGIC_COLOR +participant "result:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("mclear") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("mclear") +activate AddressBookParser + +create OneWordCommandParser +AddressBookParser -> OneWordCommandParser +activate OneWordCommandParser + +OneWordCommandParser --> AddressBookParser +deactivate OneWordCommandParser + +AddressBookParser -> OneWordCommandParser : parse("") +activate OneWordCommandParser + +create MclearCommand + +OneWordCommandParser -> MclearCommand +activate MclearCommand + +MclearCommand --> OneWordCommandParser +deactivate MclearCommand + +OneWordCommandParser --> AddressBookParser : m +deactivate OneWordCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +OneWordCommandParser -[hidden]-> AddressBookParser +destroy OneWordCommandParser + +AddressBookParser --> LogicManager : m +deactivate AddressBookParser + +LogicManager -> MclearCommand : execute() +activate MclearCommand + +MclearCommand -> Model : clearMod() + +activate Model + +Model --> MclearCommand + +deactivate Model + +create CommandResult +MclearCommand -> CommandResult +activate CommandResult + +CommandResult --> MclearCommand +deactivate CommandResult + +MclearCommand --> LogicManager : result +deactivate MclearCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index e85a00d4107..94e328b403e 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -15,6 +15,13 @@ Class ModelManager Class UserPrefs Class ReadOnlyUserPrefs +Package Module { +Class Module +Class ModuleName +Class ModuleCode +Class UniqueModuleList +} + Package Person { Class Person Class Address @@ -48,9 +55,19 @@ Person *--> Email Person *--> Address Person *--> "*" Tag +AddressBook *--> "2" UniqueModuleList +UniqueModuleList o--> "*" Module +Module *--> ModuleCode +Module *--> ModuleName +Module *--> "*" Person + Name -[hidden]right-> Phone Phone -[hidden]right-> Address Address -[hidden]right-> Email +UniqueModuleList -[hidden]right-> UniquePersonList +ModuleName -[hidden]right-> ModuleCode + ModelManager -->"1" Person : filtered list +ModelManager -->"1" Module : filtered list @enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index 6adb2e156bf..eeffc9fb527 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -21,4 +21,7 @@ JsonAddressBookStorage .left.|> AddressBookStorage JsonAddressBookStorage .down.> JsonSerializableAddressBookStorage JsonSerializableAddressBookStorage .right.> JsonSerializablePerson JsonSerializablePerson .right.> JsonAdaptedTag + +JsonSerializableAddressBookStorage .right.> JsonSerializableModule +JsonSerializableModule .right.> JsonSerializablePerson @enduml diff --git a/docs/diagrams/SwitchActivityDiagram.puml b/docs/diagrams/SwitchActivityDiagram.puml new file mode 100644 index 00000000000..adbbd923a9a --- /dev/null +++ b/docs/diagrams/SwitchActivityDiagram.puml @@ -0,0 +1,15 @@ +@startuml +start +:User executes switch command; + +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +if () then ([active semester is Semester 1]) +:Switch active semester to Semester 2; +else ([else]) +:Switch active semester to Semester 1; +endif +:Update GUI to show active semester; +stop +@enduml diff --git a/docs/diagrams/SwitchSequenceDiagram.puml b/docs/diagrams/SwitchSequenceDiagram.puml new file mode 100644 index 00000000000..e59f2318dfd --- /dev/null +++ b/docs/diagrams/SwitchSequenceDiagram.puml @@ -0,0 +1,72 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant "parser:OneWordCommandParser" as OneWordCommandParser LOGIC_COLOR +participant "s:SwitchCommand" as SwitchCommand LOGIC_COLOR +participant "result:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("switch") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("switch") +activate AddressBookParser + +create OneWordCommandParser +AddressBookParser -> OneWordCommandParser +activate OneWordCommandParser + +OneWordCommandParser --> AddressBookParser +deactivate OneWordCommandParser + +AddressBookParser -> OneWordCommandParser : parse("") +activate OneWordCommandParser + +create SwitchCommand + +OneWordCommandParser -> SwitchCommand +activate SwitchCommand + +SwitchCommand --> OneWordCommandParser +deactivate SwitchCommand + +OneWordCommandParser --> AddressBookParser : s +deactivate OneWordCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +OneWordCommandParser -[hidden]-> AddressBookParser +destroy OneWordCommandParser + +AddressBookParser --> LogicManager : s +deactivate AddressBookParser + +LogicManager -> SwitchCommand : execute() +activate SwitchCommand + +SwitchCommand -> Model : switchModuleList() + +activate Model + +Model --> SwitchCommand + +deactivate Model + +create CommandResult +SwitchCommand -> CommandResult +activate CommandResult + +CommandResult --> SwitchCommand +deactivate CommandResult + +SwitchCommand --> LogicManager : result +deactivate SwitchCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 92746f9fcf7..d6575049ff4 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -13,6 +13,8 @@ Class HelpWindow Class ResultDisplay Class PersonListPanel Class PersonCard +Class ModuleListPanel +Class ModuleCard Class StatusBarFooter Class CommandBox } @@ -34,9 +36,11 @@ MainWindow --> HelpWindow MainWindow *-down-> CommandBox MainWindow *-down-> ResultDisplay MainWindow *-down-> PersonListPanel +MainWindow *-down-> ModuleListPanel MainWindow *-down-> StatusBarFooter PersonListPanel -down-> PersonCard +ModuleListPanel -down-> ModuleCard MainWindow -left-|> UiPart @@ -44,13 +48,17 @@ ResultDisplay --|> UiPart CommandBox --|> UiPart PersonListPanel --|> UiPart PersonCard --|> UiPart +ModuleListPanel --|> UiPart +ModuleCard --|> UiPart StatusBarFooter --|> UiPart HelpWindow -down-|> UiPart PersonCard ..> Model +ModuleCard ..> Model UiManager -right-> Logic MainWindow -left-> Logic +ModuleListPanel -[hidden]left- PersonListPanel PersonListPanel -[hidden]left- HelpWindow HelpWindow -[hidden]left- CommandBox CommandBox -[hidden]left- ResultDisplay diff --git a/docs/diagrams/UnassignActivityDiagram.puml b/docs/diagrams/UnassignActivityDiagram.puml new file mode 100644 index 00000000000..508ccae49e4 --- /dev/null +++ b/docs/diagrams/UnassignActivityDiagram.puml @@ -0,0 +1,27 @@ +@startuml +start +:User executes unassign command; + +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +if() then ([no modules specified]) + :Unassign specified contact from all modules it is assigned to; +else([else]) + if () then ([all modules exist]) + if () then ([contact is assigned to + all modules specified]) + :Unassign specified contact from specified modules; + :Update GUI to show changes; + + else([else]) + : Show error message; + endif + + + else ([else]) + : Show error message; + endif +endif +stop +@enduml diff --git a/docs/diagrams/UnassignSequenceDiagram.puml b/docs/diagrams/UnassignSequenceDiagram.puml new file mode 100644 index 00000000000..77f5ce99eb7 --- /dev/null +++ b/docs/diagrams/UnassignSequenceDiagram.puml @@ -0,0 +1,73 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant "parser:UnassignCommandParser" as UnassignCommandParser LOGIC_COLOR +participant "u:UnassignCommand" as UnassignCommand LOGIC_COLOR +participant "result:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("unassign 1 m/CS2103") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("unassign 1 m/CS2103") +activate AddressBookParser + +create UnassignCommandParser +AddressBookParser -> UnassignCommandParser +activate UnassignCommandParser + +UnassignCommandParser --> AddressBookParser +deactivate UnassignCommandParser + +AddressBookParser -> UnassignCommandParser : parse("1 m/CS2103") +activate UnassignCommandParser + + +create UnassignCommand + +UnassignCommandParser -> UnassignCommand +activate UnassignCommand + +UnassignCommand --> UnassignCommandParser +deactivate UnassignCommand + +UnassignCommandParser --> AddressBookParser : u +deactivate UnassignCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +UnassignCommandParser -[hidden]-> AddressBookParser +destroy UnassignCommandParser + +AddressBookParser --> LogicManager : u +deactivate AddressBookParser + +LogicManager -> UnassignCommand : execute() +activate UnassignCommand + +UnassignCommand -> Model : unassignInstructor(1, CS2103) + +activate Model + +Model --> UnassignCommand + +deactivate Model + +create CommandResult +UnassignCommand -> CommandResult +activate CommandResult + +CommandResult --> UnassignCommand +deactivate CommandResult + +UnassignCommand --> LogicManager : result +deactivate UnassignCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/UnassignallActivityDiagram.puml b/docs/diagrams/UnassignallActivityDiagram.puml new file mode 100644 index 00000000000..6bbdca9b167 --- /dev/null +++ b/docs/diagrams/UnassignallActivityDiagram.puml @@ -0,0 +1,12 @@ +@startuml +start +:User executes unassignall command; + +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +:Unassign all contacts from all modules; +:Update GUI to show changes; + +stop +@enduml diff --git a/docs/diagrams/UnassignallSequenceDiagram.puml b/docs/diagrams/UnassignallSequenceDiagram.puml new file mode 100644 index 00000000000..1fe2e7a8f84 --- /dev/null +++ b/docs/diagrams/UnassignallSequenceDiagram.puml @@ -0,0 +1,72 @@ +@startuml +!include style.puml + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant "parser:OneWordCommandParser" as OneWordCommandParser LOGIC_COLOR +participant "u:UnassignallCommand" as UnassignallCommand LOGIC_COLOR +participant "result:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("unassignall") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("unassignall") +activate AddressBookParser + +create OneWordCommandParser +AddressBookParser -> OneWordCommandParser +activate OneWordCommandParser + +OneWordCommandParser --> AddressBookParser +deactivate OneWordCommandParser + +AddressBookParser -> OneWordCommandParser : parse("") +activate OneWordCommandParser + +create UnassignallCommand + +OneWordCommandParser -> UnassignallCommand +activate UnassignallCommand + +UnassignallCommand --> OneWordCommandParser +deactivate UnassignallCommand + +OneWordCommandParser --> AddressBookParser : u +deactivate OneWordCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +OneWordCommandParser -[hidden]-> AddressBookParser +destroy OneWordCommandParser + +AddressBookParser --> LogicManager : u +deactivate AddressBookParser + +LogicManager -> UnassignallCommand : execute() +activate UnassignallCommand + +UnassignallCommand -> Model : unassignAllInstructors() + +activate Model + +Model --> UnassignallCommand + +deactivate Model + +create CommandResult +UnassignallCommand -> CommandResult +activate CommandResult + +CommandResult --> UnassignallCommand +deactivate CommandResult + +UnassignallCommand --> LogicManager : result +deactivate UnassignallCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/images/AfterAssignment.png b/docs/images/AfterAssignment.png new file mode 100644 index 00000000000..0511ea10e58 Binary files /dev/null and b/docs/images/AfterAssignment.png differ diff --git a/docs/images/AssignActivityDiagram.png b/docs/images/AssignActivityDiagram.png new file mode 100644 index 00000000000..b011dabb7aa Binary files /dev/null and b/docs/images/AssignActivityDiagram.png differ diff --git a/docs/images/AssignSequenceDiagram.png b/docs/images/AssignSequenceDiagram.png new file mode 100644 index 00000000000..2d394e9a5ac Binary files /dev/null and b/docs/images/AssignSequenceDiagram.png differ diff --git a/docs/images/BeforeAssignment.png b/docs/images/BeforeAssignment.png new file mode 100644 index 00000000000..1cf5ff60a9c Binary files /dev/null and b/docs/images/BeforeAssignment.png differ diff --git a/docs/images/CclearActivityDiagram.png b/docs/images/CclearActivityDiagram.png new file mode 100644 index 00000000000..12ce95855cd Binary files /dev/null and b/docs/images/CclearActivityDiagram.png differ diff --git a/docs/images/CclearSequenceDiagram.png b/docs/images/CclearSequenceDiagram.png new file mode 100644 index 00000000000..598e5661c5e Binary files /dev/null and b/docs/images/CclearSequenceDiagram.png differ diff --git a/docs/images/ClearContactsSequenceDiagram.png b/docs/images/ClearContactsSequenceDiagram.png new file mode 100644 index 00000000000..1890a4531b9 Binary files /dev/null and b/docs/images/ClearContactsSequenceDiagram.png differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index fa327b39618..4670651e0ac 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/DelmodActivityDiagram.png b/docs/images/DelmodActivityDiagram.png new file mode 100644 index 00000000000..053a5f64902 Binary files /dev/null and b/docs/images/DelmodActivityDiagram.png differ diff --git a/docs/images/DelmodSequenceDiagram.png b/docs/images/DelmodSequenceDiagram.png new file mode 100644 index 00000000000..fd75f5f341b Binary files /dev/null and b/docs/images/DelmodSequenceDiagram.png differ diff --git a/docs/images/EditedJSONFile.png b/docs/images/EditedJSONFile.png new file mode 100644 index 00000000000..66b442c3064 Binary files /dev/null and b/docs/images/EditedJSONFile.png differ diff --git a/docs/images/FindActivityDiagram.png b/docs/images/FindActivityDiagram.png new file mode 100644 index 00000000000..4fd1c764f72 Binary files /dev/null and b/docs/images/FindActivityDiagram.png differ diff --git a/docs/images/FindSequenceDiagram.png b/docs/images/FindSequenceDiagram.png new file mode 100644 index 00000000000..023f75cdf60 Binary files /dev/null and b/docs/images/FindSequenceDiagram.png differ diff --git a/docs/images/FindmodActivityDiagram.png b/docs/images/FindmodActivityDiagram.png new file mode 100644 index 00000000000..fe4ee320cca Binary files /dev/null and b/docs/images/FindmodActivityDiagram.png differ diff --git a/docs/images/FindmodSequenceDiagram.png b/docs/images/FindmodSequenceDiagram.png new file mode 100644 index 00000000000..9a1451c5462 Binary files /dev/null and b/docs/images/FindmodSequenceDiagram.png differ diff --git a/docs/images/InstructorBug.png b/docs/images/InstructorBug.png new file mode 100644 index 00000000000..e6940978f41 Binary files /dev/null and b/docs/images/InstructorBug.png differ diff --git a/docs/images/JSONFileComposite.png b/docs/images/JSONFileComposite.png new file mode 100644 index 00000000000..95697e377aa Binary files /dev/null and b/docs/images/JSONFileComposite.png differ diff --git a/docs/images/MclearActivityDiagram.png b/docs/images/MclearActivityDiagram.png new file mode 100644 index 00000000000..255a62481ef Binary files /dev/null and b/docs/images/MclearActivityDiagram.png differ diff --git a/docs/images/MclearCommandSequenceDiagram.png b/docs/images/MclearCommandSequenceDiagram.png new file mode 100644 index 00000000000..4f2c79069cb Binary files /dev/null and b/docs/images/MclearCommandSequenceDiagram.png differ diff --git a/docs/images/MclearSequenceDiagram.png b/docs/images/MclearSequenceDiagram.png new file mode 100644 index 00000000000..95098ed8dfd Binary files /dev/null and b/docs/images/MclearSequenceDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 280064118cf..dde8fea2a79 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/OriginalJSONFile.png b/docs/images/OriginalJSONFile.png new file mode 100644 index 00000000000..b11070eb1e0 Binary files /dev/null and b/docs/images/OriginalJSONFile.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index d87c1216820..c094ecfee86 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/SwitchActivityDiagram.png b/docs/images/SwitchActivityDiagram.png new file mode 100644 index 00000000000..1940ad30b39 Binary files /dev/null and b/docs/images/SwitchActivityDiagram.png differ diff --git a/docs/images/SwitchSequenceDiagram.png b/docs/images/SwitchSequenceDiagram.png new file mode 100644 index 00000000000..305197f068a Binary files /dev/null and b/docs/images/SwitchSequenceDiagram.png differ diff --git a/docs/images/TagBug.png b/docs/images/TagBug.png new file mode 100644 index 00000000000..fdb3a282c10 Binary files /dev/null and b/docs/images/TagBug.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..2a0d3fbf32e 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 7b4b3dbea45..84bc0eeb921 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UiInit.png b/docs/images/UiInit.png new file mode 100644 index 00000000000..f61415bc113 Binary files /dev/null and b/docs/images/UiInit.png differ diff --git a/docs/images/UnassignActivityDiagram.png b/docs/images/UnassignActivityDiagram.png new file mode 100644 index 00000000000..e5689d2b455 Binary files /dev/null and b/docs/images/UnassignActivityDiagram.png differ diff --git a/docs/images/UnassignSequenceDiagram.png b/docs/images/UnassignSequenceDiagram.png new file mode 100644 index 00000000000..9a1451c5462 Binary files /dev/null and b/docs/images/UnassignSequenceDiagram.png differ diff --git a/docs/images/UnassignallActivityDiagram.png b/docs/images/UnassignallActivityDiagram.png new file mode 100644 index 00000000000..c6fd833b690 Binary files /dev/null and b/docs/images/UnassignallActivityDiagram.png differ diff --git a/docs/images/UnassignallSequenceDiagram.png b/docs/images/UnassignallSequenceDiagram.png new file mode 100644 index 00000000000..8080db71941 Binary files /dev/null and b/docs/images/UnassignallSequenceDiagram.png differ diff --git a/docs/images/drake25122000.png b/docs/images/drake25122000.png new file mode 100644 index 00000000000..1ff5d5e20b1 Binary files /dev/null and b/docs/images/drake25122000.png differ diff --git a/docs/images/erinmayg.png b/docs/images/erinmayg.png new file mode 100644 index 00000000000..3b7df9f22a8 Binary files /dev/null and b/docs/images/erinmayg.png differ diff --git a/docs/images/florenciamartina.png b/docs/images/florenciamartina.png new file mode 100644 index 00000000000..7f67b4a890f Binary files /dev/null and b/docs/images/florenciamartina.png differ diff --git a/docs/images/justintzuriel.png b/docs/images/justintzuriel.png new file mode 100644 index 00000000000..4bccd68f4bf Binary files /dev/null and b/docs/images/justintzuriel.png differ diff --git a/docs/images/jzwoo.png b/docs/images/jzwoo.png new file mode 100644 index 00000000000..cf38f022781 Binary files /dev/null and b/docs/images/jzwoo.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..1b106b50d51 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,17 +1,16 @@ --- layout: page -title: AddressBook Level-3 +title: FaculType --- -[![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/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/AY2021S1-CS2103-T14-1/tp/actions) +[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/AY2021S1-CS2103-T14-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. +**FaculType** is a desktop app for managing faculty members and their modules, optimized for use via a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). +* If you are interested in using FaculType, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). +* If you are interested about developing FaculType, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. **Acknowledgements** diff --git a/docs/team/drake25122000.md b/docs/team/drake25122000.md new file mode 100644 index 00000000000..8d81571b4aa --- /dev/null +++ b/docs/team/drake25122000.md @@ -0,0 +1,57 @@ +--- +layout: page +title: Christian Drake Martin's Project Portofolio Page +--- + +## Project: FaculType + +FaculType is a desktop app for managing faculty members and their modules, optimized for use via a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI) created with JavaFX. It is written in Java, and has about 11 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: Added the ability for user to add office and department to the attribute of the contacts. + * What it does: allows the user to include office and department as the attribute of their contacts. + * Justification: This feature improves the product because users can now find their contacts' office. + * Highlights: This feature requires and understanding of the person model. + +* **New Feature**: Added the ability for user to clear all contacts from the contact list. + * What it does: allows the user to clear all contacts from the contact list. + * Justification: This feature ease the users if they want to delete all contacts instead of deleting them one by one. + * Highlights: This feature requires and understanding of the contact list model. + +
+ +* **New Feature**: Added the ability for user to clear all modules from the module list. + * What it does: allows the user to clear all modules from the module list. + * Justification: This feature ease the users if they want to delete all modules instead of deleting them one by one. + * Highlights: This feature requires and understanding of the module list model. +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=drake25122000&sort=groupTitle&sortWithin=title&since=2020-08-14&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other&tabOpen=false) + +* **Project management**: + * Managed releases `v1.1` - `v1.3` (2 releases) on GitHub + * Code quality manager + +* **Enhancements to existing features**: + * Add office attribute to person class [\#62](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/62) + * Add department attribute to person class [\#60](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/60) + +* **Documentation**: + * User Guide: + * Added documentation for the features `mclear` [\#93](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/93) + * Added documentation for the features `cclear` [\#106](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/106) + * Developer Guide: + * Added implementation details of the `cclear` feature. [\#248](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/248) + * Added implementation details of the `mclear` feature. [\#248](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/248) + +* **Community**: + * PRs reviewed (with non-trivial review comments): + [\#122](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/122), + [\#123](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/123) + * Helped to notify bugs for another team (Issues + [\#6](https://github.com/drake25122000/ped/issues/6), + [\#5](https://github.com/drake25122000/ped/issues/5), + [\#4](https://github.com/drake25122000/ped/issues/4), + [\#3](https://github.com/drake25122000/ped/issues/3), + [\#2](https://github.com/drake25122000/ped/issues/2), + [\#1](https://github.com/drake25122000/ped/issues/1) + ) diff --git a/docs/team/erinmayg.md b/docs/team/erinmayg.md new file mode 100644 index 00000000000..0c2762be1a3 --- /dev/null +++ b/docs/team/erinmayg.md @@ -0,0 +1,123 @@ +--- +layout: page +title: Erin May's Project Portfolio Page +--- + +## Project: FaculType + +FaculType is a desktop app for managing faculty members and their modules, optimized for use via a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI) created with JavaFX. It is written in Java, and has about 11 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: Implemented Modules (including its attributes and UniqueModuleList) + * What it is: A representation of modules that are available in the user's university/college. + * Justification: This feature improves the product by allowing users to insert modules into the app. + * Highlights: This feature is a foundation for module-related commands to be added in the future. It required an + in-depth analysis and understanding of how the application works as a whole (from displaying the components to + how it stores the data). + +* **New Feature**: Added the ability to list all modules + * What it does: A command to show all available modules in the active semester in the module list. + * Justification: This feature improves the product by allowing users to only reset the view of the module list + while still keeping the filtered contacts list. + * Highlights: This feature required an understanding of the model class. + +* **New Feature**: Added the ability to detect duplicate prefixes + * What it does: Detects whether there's a duplicate prefix provided by the user. + * Justification: Restricts the user by forbidding multiple prefixes in commands which doesn't + support multiple prefixes of the same type. + * Highlights: This feature required an understanding of how the `ArgumentMultimap` class works. + +* **New Feature**: Added the ability to detect any prefix from the list of required prefixes + * What it does: Checks whether there exists at least one prefix from the list of supported prefixes by a command. + * Justification: This feature improves the app by allowing commands (such as `find` and `findmod`) to be more + flexible by not requiring users to provide all supported prefixes (users only need to input at least one prefix). + * Highlights: This feature required an understanding of how the `ArgumentMultimap` class works. + +
+ +* **New Feature**: Displays the module list and a view of switching semesters + * What it does: Switches the tabs in the GUI to represent the switching of semesters. + * Justification: This feature improves the product as it gives users a graphical view of all the modules in + the active semester (Semester 1 or Semester 2). + * Highlights: This feature required an in-depth understanding of how JavaFX works (both in the design aspect and + the functional coding aspect) and an analysis of all existing components and its CSS attributes. + * Credits: Module TabPane color scheme [link](https://stackoverflow.com/questions/30642032/how-to-get-rid-of-the-grey-selection-border-in-javafx). + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=erinmayg) + +* **Enhancements to existing features**: + * Enables listing of all contacts, modules, or both (Pull requests + [\#96](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/96), + [\#98](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/96)) + * Allow finding by attributes (Pull requests + [\#105](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/105), + [\#117](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/117), + [\#124](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/124), + [\#133](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/133)) + * Updated the GUI to better suit the app (Pull requests + [\#86](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/86), + [\#145](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/145), + [\#149](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/149), + [\#171](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/171), + [\#172](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/172), + [\#174](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/174), + [\#183](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/183), + [\#187](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/187), + [\#220](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/220), + [\#226](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/226), + [\#245](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/245), + [\#247](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/247)) + * Wrote additional test utils for new features (Pull request + [\#61](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/61)) + * Wrote additional test cases for new and existing features (Pull requests + [\#61](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/61), + [\#66](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/66), + [\#96](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/96), + [\#98](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/98), + [\#105](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/105), + [\#117](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/117), + [\#124](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/124)) + +* **Documentation**: + * User Guide: + * Added documentation for the features `find`, `mlist`, and `clist`: + [\#96](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/96), + [\#98](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/98), + [\#105](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/149) + * Added current bugs: + [\#278](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/278) + * Developer Guide: + * Updated the class diagrams of Model, Storage, and UI: + [\#130](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/130), + [\#239](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/239) + * Added implementation details of the `find` feature: + [\#130](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/130), + [\#229](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/229) + * Added the use cases for all features: + [\#31](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/31), + [\#96](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/96), + [\#240](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/240), + [\#262](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/262) + * Added the manual testing for most features: + [\#262](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/262), + [\#272](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/272) + * Added current bugs: + [\#278](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/278) + +* **Community**: + * PRs reviewed (with non-trivial review comments): + [\#64](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/64), + [\#93](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/93), + [\#121](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/121), + [\#122](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/122), + [\#123](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/123), + [\#135](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/135), + [\#222](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/222) + * Reported bugs and suggestions for other teams in the class (examples: + [1](https://github.com/erinmayg/ped/issues/1), + [2](https://github.com/erinmayg/ped/issues/3), + [3](https://github.com/erinmayg/ped/issues/3), + [4](https://github.com/erinmayg/ped/issues/7), + [5](https://github.com/erinmayg/ped/issues/8), + [6](https://github.com/erinmayg/ped/issues/8)) diff --git a/docs/team/florenciamartina.md b/docs/team/florenciamartina.md new file mode 100644 index 00000000000..c6cb2fea513 --- /dev/null +++ b/docs/team/florenciamartina.md @@ -0,0 +1,58 @@ +--- +layout: page +title: Florencia Martina's Project Portfolio Page +--- + +## Project: FaculType + +FaculType is a desktop app for managing faculty members and their modules, optimized for use via a Command Line Interface (CLI) while still having the benefits of a +Graphical User Interface (GUI) created with JavaFX. It is written in Java, and has about 11 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to unassign a contact from modules. + * What it does: allows the user to unassign a contact from modules specified. + * Justification: This feature improves the product significantly because instructors may retire or assigned to teach other modules. Unassign makes it easier to + update the assignment. + * Highlights: This enhancement required an in-depth analysis of design alternatives because there are a lot of possible command combination. It also required an + understanding of existing assign feature. Unassign can be used to unassign specified contact from specified modules or unassign the contact from all modules it is + assigned to, depending on the command format. + +* **New Feature**: Added the ability to unassign all contacts from all modules. + * What it does: allows the user to unassign all contacts from all modules in FaculType. + * Justification: This feature improves the product significantly to ease major changes in the teaching staff structure. It is also used by cclear to delete all + assignments prior to deleting all contacts to avoid inconsistency or invalid data. + * Highlights: This enhancement required a rigid implementation because it affects another feature (cclear command) in the FaculType. + +* **New Feature**: Added the ability to add modules. + * What it does: allows the user to add more modules to FaculType. + * Justification: This feature improves the product significantly because university curriculum is dynamic. Modules might be added or updated to adjust to the + current curriculum. + * Highlights: This feature required an understanding of existing Module, UniqueModuleList, and Model class. It needs a lot of checking to ensure there are no + duplicate modules or modules with invalid format. + +
+ +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=florenciamartina&sort=groupTitle&sortWithin=title&since=2020-08-14&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other&tabOpen=true&tabType=authorship&tabAuthor=florenciamartina&tabRepo=AY2021S1-CS2103-T14-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code) + +* **Enhancements to existing features**: + * Updated all one-word commands to disallow arguments [\#180](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/180), [\#219](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/219) + * Wrote additional tests for existing features to increase coverage (Pull requests [\#64](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/64), [\#114](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/114), [\#115](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/115), [\#135](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/135), [\#180](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/180)) + * Increased font size to increase readability [\#147](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/147) + +* **Documentation**: + * User Guide: + * Added documentation for the features `addmod`, `findmod`, `assign`, `mclear`, and `cclear` [\#36](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/36), [\#90](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/90) + * Update existing documentation of features `clear`: [\#90](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/90) + * Developer Guide: + * Added NFRs. [\#29](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/29) + * Updated glossary and user stories. [\#92](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/92), [\#241](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/241) + * Added implementation details of the `unassign` and `unassignall` feature. [\#129](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/129), [\#230](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/230) + +* **Community**: + * PRs reviewed (with non-trivial review comments): [\#61](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/61), [\#89](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/89) + * Reported bugs and suggestions for other teams in the class (examples: [1](https://github.com/florenciamartina/ped/issues/2), [2](https://github.com/florenciamartina/ped/issues/7), [3](https://github.com/florenciamartina/ped/issues/8), [4](https://github.com/florenciamartina/ped/issues/10)) + +* **Tools**: + * Integrated a test coverage plugin (CodeCov) to the team repo + diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md index 1f1e9e6f6db..4204344f7ee 100644 --- a/docs/team/johndoe.md +++ b/docs/team/johndoe.md @@ -1,11 +1,11 @@ --- layout: page -title: John Doe's Project Portfolio Page +title: Project Portfolio Page --- -## Project: AddressBook Level 3 +## Project: FaculType -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. +FaculType is a desktop app for managing faculty members and their modules, optimized for use via a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI) created with JavaFX. It is written in Java, and has about 11 kLoC. Given below are my contributions to the project. diff --git a/docs/team/justintzuriel.md b/docs/team/justintzuriel.md new file mode 100644 index 00000000000..e1eebb83e3a --- /dev/null +++ b/docs/team/justintzuriel.md @@ -0,0 +1,77 @@ +--- +layout: page +title: Project Portfolio Page +--- + +## Project: FaculType + +FaculType is a desktop app for managing faculty members and their modules, optimized for use via a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI) created with JavaFX. It is written in Java, and has about 11 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to assign contacts to modules + * What it does: Allows the user to assign a particular contact to one or more modules in the active semester. + * Justification: This feature improves the product by helping the user keep track of the assignments of faculty members to the modules that they instruct. This is one of the main value propositions of FaculType, + as it allows faculty leaders to manage the relationship between faculty members and modules instead of managing faculty members and faculty modules separately. + * Highlights: This enhancement required an in-depth understanding of how data is stored inside the `Model` component. Development could only be done after the module management features were finished. + +* **New Feature**: Added the ability to switch between semesters + * What it does: Allows the user to switch to the other semester's module list. + * Justification: This feature improves the product by allowing the user to switch to another module configuration quickly. This is also one of the main value propositions of FaculType, + because a particular semester/term tends to not change a lot from year to year, but the modules offered in Semester 1 might differ greatly from those offered in Semester 2 (and their instructors as well). + The ability to switch to another configuration is much more convenient than having to erase and re-add module data each semester. + * Highlights: This enhancement required an in-depth analysis of design alternatives. A change to the `AddressBook` structure was also required. This feature was developed last and the result of all module management features now depend on the active semester. + +
+ +* **New Feature**: Added the ability to add remarks + * What it does: Allows the user to add additional information or personal remarks to contacts. + * Justification: This feature improves the product by helping the user keep track of useful additional information about the contact that cannot be stored inside the contact's attributes. + * Highlights: This enhancement required an understanding of how the application parses and executes commands. This was one of the first enhancements made for FaculType. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=justintzuriel&sort=groupTitle&sortWithin=title&since=2020-08-14&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other&tabOpen=true&tabType=authorship&tabAuthor=justintzuriel&tabRepo=AY2021S1-CS2103-T14-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other) + +* **Project management**: + * Managed all team releases (`v1.1` - `v1.4`) on GitHub + * Managed the issue tracker and created every issue for each iteration + * Assigned issues and delegated tasks to team members + * Led the weekly project meetings + * Recorded all demo videos + +* **Enhancements to existing features**: + * Designed the FaculType icon (Pull requests [\#94](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/94)) + * Modified command messages to be more descriptive (Pull requests [\#181](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/181)) + * Wrote additional tests for `ModelManager` to increase coverage (Pull requests [\#227](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/227)) + * Added sample modules (Pull requests [\#178](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/178)) + +* **Documentation**: + * User Guide: + * Added documentation for the features `edit`, `remark` and `switch` + [\#32](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/32), + [\#33](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/33), + [\#161](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/161) + * Add current bugs and upcoming features [\#263](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/263) + * Maintained correctness and consistency from time to time: + [\#25](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/25), + [\#38](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/38), + [\#49](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/49), + [\#122](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/122), + [\#178](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/178), + [\#182](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/182), + [\#184](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/184), + [\#186](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/186), + [\#189](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/189), + [\#225](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/225) + * Developer Guide: + * Added implementation details of the `assign` and `switch` features [\#261](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/261), [\#128](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/128) + * Added manual testing instructions for the `assign` and `switch` features [\#261](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/261) + * Added `effort` appendix [\#281](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/281) + * Add current bugs and upcoming features [\#263](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/263) + * Maintained correctness and consistency from time to time: + [\#41](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/41), + [\#47](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/47), + [\#261](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/261) + +* **Community**: + * PRs reviewed (with non-trivial review comments): [\#61](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/61), [\#95](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/95), [\#144](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/114) + * Reported bugs and suggestions for other teams in the class (examples: [4](https://github.com/justintzuriel/ped/issues/4), [5](https://github.com/justintzuriel/ped/issues/5), [6](https://github.com/justintzuriel/ped/issues/6), [7](https://github.com/justintzuriel/ped/issues/7)) diff --git a/docs/team/jzwoo.md b/docs/team/jzwoo.md new file mode 100644 index 00000000000..8dd769e8a37 --- /dev/null +++ b/docs/team/jzwoo.md @@ -0,0 +1,61 @@ +--- +layout: page +title: Woo Jian Zhe's Project Portfolio Page +--- + +## Project: FaculType + +FaculType is a desktop app for managing faculty members and their modules, optimized for use via a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI) created with JavaFX. It is written in Java, and has about 11 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to delete existing modules. + * What it does: Allows the user to delete existing modules in the module list. + * Justification: This feature improves the product because it allows the user to remove modules as part of the module management capability of FaculType. + * Highlights: This enhancement required an in-depth analysis of design alternatives. The implementation was challenging because it required the use of module codes instead of the index number for deletion. + +* **New Feature**: Added a search function for modules. + * What it does: Allows the user to search for modules by different parameters. + * Justification: This feature improves the product significantly because it allows for more convenient management when there are a lot of modules involved. + * Highlights: This enhancement required an in-depth analysis of design alternatives. The implementation was challenging due to the different parameters involved. There was a need to make the search function focused but also flexible enough such that a more general search could be performed. + +* **New Feature**: Added the ability to match SubStrings. + * What it does: Checks if a substring is part of a within a string, while ignoring the case of the strings. + * Justification: This feature improves the product because it allows for a more flexible search option in `find` and `findmod`. + * Highlights: This feature requires understanding of how to manipulate strings and the requirements of our search functions. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=jzwoo) + +* **Enhancements to existing features**: + * Added icons for contact attributes (Pull requests + [\#150](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/150)) + * Wrote additional tests for existing features to increase coverage (Pull requests + [\#69](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/69), + [\#89](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/89), + [\#242](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/242)) + * Added a model stub class for testing (Pull requests + [\#146](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/146)) + * Added checks for duplicate prefixes of multiple commands (Pull requests + [\#222](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/222)) + +* **Documentation**: + * User Guide: + * Added documentation for the features `delmod` and `findmod` + [\#27](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/27), + [\#228](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/228) + * Developer Guide: + * Added implementation details of the `delmod` and `findmod` feature. + [\#136](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/136), + [\#228](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/228) + * Added and modified glossary + [\#24](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/24), + [\#243](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/243) + +* **Community**: + * PRs reviewed (with non-trivial review comments): + [\#105](https://github.com/AY2021S1-CS2103-T14-1/tp/pull/105) + * Reported bugs and suggestions for other teams in the class (examples: + [1](https://github.com/jzwoo/ped/issues/1), + [2](https://github.com/jzwoo/ped/issues/2), + [3](https://github.com/jzwoo/ped/issues/3), + [4](https://github.com/jzwoo/ped/issues/4)) diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index e5cfb161b73..bf448b1cca6 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -36,7 +36,7 @@ */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 6, 0, true); + public static final Version VERSION = new Version(1, 4, 0, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -48,9 +48,8 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing FaculType ]==========================="); super.init(); - AppParameters appParameters = AppParameters.parse(getParameters()); config = initConfig(appParameters.getConfigPath()); @@ -78,7 +77,7 @@ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { ReadOnlyAddressBook initialData; try { addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { + if (addressBookOptional.isEmpty()) { logger.info("Data file not found. Will be starting with a sample AddressBook"); } initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); @@ -167,13 +166,13 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting FaculType " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping FaculType ] ============================="); try { storage.saveUserPrefs(model.getUserPrefs()); } catch (IOException e) { diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/seedu/address/commons/core/GuiSettings.java index ba33653be67..84229f4d128 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/seedu/address/commons/core/GuiSettings.java @@ -10,8 +10,9 @@ */ public class GuiSettings implements Serializable { - private static final double DEFAULT_HEIGHT = 600; - private static final double DEFAULT_WIDTH = 740; + private static final double DEFAULT_HEIGHT = 800; + private static final double DEFAULT_WIDTH = 982; + private static final double MINIMUM_WIDTH = 982; private final double windowWidth; private final double windowHeight; @@ -35,6 +36,10 @@ public GuiSettings(double windowWidth, double windowHeight, int xPosition, int y windowCoordinates = new Point(xPosition, yPosition); } + public static double getMinimumWidth() { + return MINIMUM_WIDTH; + } + public double getWindowWidth() { return windowWidth; } diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e469..4d39efe7f90 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -9,5 +9,18 @@ public class Messages { public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_MODULES_LISTED = "%1$d modules listed!"; + public static final String MESSAGE_MODULE_DOES_NOT_EXIST = "%1$s does not exist in the active semester"; + public static final String MESSAGE_DUPLICATE_MODULE = "%1$s already exists in the active semester"; + public static final String MESSAGE_DUPLICATE_MODULE_CODE = "Duplicate module code specified: %s"; + public static final String MESSAGE_INSTRUCTOR_ALREADY_ASSIGNED = "%1$s is already assigned to %2$s"; + public static final String MESSAGE_INSTRUCTOR_IS_NOT_ASSIGNED = "%1$s is not assigned to %2$s"; + public static final String MESSAGE_PERSON_IS_NOT_AN_INSTRUCTOR = "%1$s does not instruct any modules"; + public static final String MESSAGE_DUPLICATE_PREFIX = "Prefix %s has a duplicate"; + public static final String MESSAGE_EMPTY_KEYWORD = "Keyword cannot be empty"; + public static final String MESSAGE_INVALID_MODULE_CODE_KEYWORD = "Module code keywords should be a single word"; + public static final String MESSAGE_INVALID_NAME_KEYWORD = "Name keywords should be alphanumeric!"; + public static final String MESSAGE_INVALID_PHONE_KEYWORD = "Phone keywords should be numeric!"; + public static final String MESSAGE_NON_EMPTY_ARGUMENT = "%s should not be followed by any characters!"; } diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java index 61cc8c9a1cb..a7c0e498c0b 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/address/commons/util/StringUtil.java @@ -38,6 +38,27 @@ public static boolean containsWordIgnoreCase(String sentence, String word) { .anyMatch(preppedWord::equalsIgnoreCase); } + /** + * Returns true if the {@code sentence} contains the {@code sub word} or {@code word}. + * Ignores case, and both sub word and full word can return true. + *
examples:
+     *       containsWordIgnoreCase("ABc def", "abc") == true
+     *       containsWordIgnoreCase("ABc def", "DEF") == true
+     *       containsWordIgnoreCase("ABc def", "AB") == true //since it is a sub word match
+     *       
+ * @param sentence cannot be null + * @param word cannot be null, cannot be empty. must be a single word + */ + public static boolean containsSubWordOrWordIgnoreCase(String sentence, String word) { + requireNonNull(sentence); + requireNonNull(word); + String preppedWord = word.trim().toLowerCase(); + checkArgument(!preppedWord.isEmpty(), "Word parameter cannot be empty"); + checkArgument(preppedWord.split("\\s+").length == 1, "Word parameter should be a single word"); + String preppedSentence = sentence.toLowerCase(); + return preppedSentence.contains(preppedWord); + } + /** * Returns a detailed message of the t, including the stack trace. */ diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..fa68dfb7185 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -8,6 +8,7 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.module.Module; import seedu.address.model.person.Person; /** @@ -30,14 +31,22 @@ public interface Logic { */ ReadOnlyAddressBook getAddressBook(); - /** Returns an unmodifiable view of the filtered list of persons */ + /** Returns an unmodifiable view of the filtered list of persons. */ ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered list of modules. */ + ObservableList getFilteredModuleList(); + /** * Returns the user prefs' address book file path. */ Path getAddressBookFilePath(); + /** + * Gets the current semester + */ + int getSemester(); + /** * Returns the user prefs' GUI settings. */ diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9d9c6d15bdc..d10828402cd 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -14,6 +14,7 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.module.Module; import seedu.address.model.person.Person; import seedu.address.storage.Storage; @@ -54,6 +55,11 @@ public CommandResult execute(String commandText) throws CommandException, ParseE return commandResult; } + @Override + public int getSemester() { + return model.getSemester(); + } + @Override public ReadOnlyAddressBook getAddressBook() { return model.getAddressBook(); @@ -64,6 +70,11 @@ public ObservableList getFilteredPersonList() { return model.getFilteredPersonList(); } + @Override + public ObservableList getFilteredModuleList() { + return model.getFilteredModuleList(); + } + @Override public Path getAddressBookFilePath() { return model.getAddressBookFilePath(); diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index 71656d7c5c8..5e0d1cab6f0 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -1,9 +1,10 @@ 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_DEPARTMENT; 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_OFFICE; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; @@ -23,18 +24,20 @@ public class AddCommand extends Command { + PREFIX_NAME + "NAME " + PREFIX_PHONE + "PHONE " + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_DEPARTMENT + "DEPARTMENT " + + PREFIX_OFFICE + "OFFICE " + "[" + 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_DEPARTMENT + "Computer Science " + + PREFIX_OFFICE + "COM2-02-01 " + 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"; + public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in FaculType"; private final Person toAdd; diff --git a/src/main/java/seedu/address/logic/commands/AddModCommand.java b/src/main/java/seedu/address/logic/commands/AddModCommand.java new file mode 100644 index 00000000000..721903b0f09 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddModCommand.java @@ -0,0 +1,58 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_MODULE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_CODE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_NAME; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.module.Module; + +/** + * Adds a person to the address book. + */ +public class AddModCommand extends Command { + + public static final String COMMAND_WORD = "addmod"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a module to FaculType.\n" + + "Parameters: " + + PREFIX_MODULE_CODE + "MODULE_CODE " + + PREFIX_MODULE_NAME + "MODULE_NAME\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_MODULE_CODE + "CS2103 " + + PREFIX_MODULE_NAME + "Software Engineering "; + + public static final String MESSAGE_SUCCESS = "New module added: %1$s"; + + private final Module moduleToAdd; + + /** + * Creates an AddCommand to add the specified {@code Module} + */ + public AddModCommand(Module module) { + requireNonNull(module); + moduleToAdd = module; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.hasModule(moduleToAdd)) { + throw new CommandException(String.format(MESSAGE_DUPLICATE_MODULE, + moduleToAdd.getModuleCode())); + } + + model.addModule(moduleToAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, moduleToAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddModCommand // instanceof handles nulls + && moduleToAdd.equals(((AddModCommand) other).moduleToAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/AssignCommand.java b/src/main/java/seedu/address/logic/commands/AssignCommand.java new file mode 100644 index 00000000000..8eb2f7960e1 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AssignCommand.java @@ -0,0 +1,98 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INSTRUCTOR_ALREADY_ASSIGNED; +import static seedu.address.commons.core.Messages.MESSAGE_MODULE_DOES_NOT_EXIST; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_CODE; + +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.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.person.Person; + +/** + * Assigns an instructor to one or more modules. + */ +public class AssignCommand extends Command { + + public static final String COMMAND_WORD = "assign"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Assigns an instructor to one or more modules.\n" + + "Parameters: INDEX (must be a positive integer) " + + PREFIX_MODULE_CODE + "MODULE_CODE " + + "[" + PREFIX_MODULE_CODE + "MODULE_CODE]...\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_MODULE_CODE + "CS2103"; + + public static final String MESSAGE_ADD_ASSIGNMENT_SUCCESS = "Assigned %1$s to %2$s"; + + private final Index index; + private final Set moduleCodes; + + /** + * @param index of the person in the filtered person list to be assigned + * @param moduleCodes of modules the person would be assigned to + */ + public AssignCommand(Index index, Set moduleCodes) { + requireAllNonNull(index, moduleCodes); + + this.index = index; + this.moduleCodes = moduleCodes; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + List lastShownList = model.getFilteredPersonList(); + + assert lastShownList != null; + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person instructor = lastShownList.get(index.getZeroBased()); + StringBuilder moduleString = new StringBuilder(); + + assert instructor != null; + + for (ModuleCode moduleCode: moduleCodes) { + if (!model.hasModuleCode(moduleCode)) { + throw new CommandException(String.format(MESSAGE_MODULE_DOES_NOT_EXIST, + moduleCode)); + } + if (model.moduleCodeHasInstructor(moduleCode, instructor)) { + throw new CommandException(String.format(MESSAGE_INSTRUCTOR_ALREADY_ASSIGNED, + instructor.getName(), moduleCode)); + } + } + + for (ModuleCode moduleCode: moduleCodes) { + model.assignInstructor(instructor, moduleCode); + moduleString.append(moduleCode).append(", "); + } + + assert moduleString.length() > 2; + + moduleString.delete(moduleString.length() - 2, moduleString.length()); + + return new CommandResult(String.format(MESSAGE_ADD_ASSIGNMENT_SUCCESS, + instructor.getName(), moduleString)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AssignCommand // instanceof handles nulls + && index.equals(((AssignCommand) other).index) + && moduleCodes.equals(((AssignCommand) other).moduleCodes)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/CclearCommand.java b/src/main/java/seedu/address/logic/commands/CclearCommand.java new file mode 100644 index 00000000000..c1b19945013 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/CclearCommand.java @@ -0,0 +1,38 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + +public class CclearCommand extends Command { + + public static final String COMMAND_WORD = "cclear"; + + public static final String MESSAGE_SUCCESS = "All contacts deleted"; + + public static final String MESSAGE_FAIL = "Contact list is already empty"; + + public static final String MESSAGE_USAGE = "\n" + COMMAND_WORD + + ": Clears all the contacts from the list.\n" + + "Example: " + COMMAND_WORD; + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.isEmptyPersonList()) { + throw new CommandException(MESSAGE_FAIL); + } else { + model.clearContacts(); + return new CommandResult(MESSAGE_SUCCESS); + } + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CclearCommand); // instanceof handles nulls + } +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java deleted file mode 100644 index 9c86b1fa6e4..00000000000 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ /dev/null @@ -1,23 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.model.AddressBook; -import seedu.address.model.Model; - -/** - * Clears the address book. - */ -public class ClearCommand extends Command { - - public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; - - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.setAddressBook(new AddressBook()); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/ClistCommand.java b/src/main/java/seedu/address/logic/commands/ClistCommand.java new file mode 100644 index 00000000000..4101eacd4c7 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ClistCommand.java @@ -0,0 +1,30 @@ +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 ClistCommand extends Command { + + public static final String COMMAND_WORD = "clist"; + public static final String MESSAGE_SUCCESS = "Listed all persons"; + public static final String MESSAGE_USAGE = "\n" + COMMAND_WORD + " : Lists all contacts\n" + + "Example: " + COMMAND_WORD; + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(MESSAGE_SUCCESS); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ClistCommand); // instanceof handles nulls + } +} diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java index 92f900b7916..6db66308f3b 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/address/logic/commands/CommandResult.java @@ -14,15 +14,19 @@ public class CommandResult { /** Help information should be shown to the user. */ private final boolean showHelp; + /** Switches the active semester. */ + private final boolean switchSem; + /** The application should exit. */ private final boolean exit; /** * Constructs a {@code CommandResult} with the specified fields. */ - public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { + public CommandResult(String feedbackToUser, boolean showHelp, boolean switchSem, boolean exit) { this.feedbackToUser = requireNonNull(feedbackToUser); this.showHelp = showHelp; + this.switchSem = switchSem; this.exit = exit; } @@ -31,7 +35,7 @@ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { * and other fields set to their default value. */ public CommandResult(String feedbackToUser) { - this(feedbackToUser, false, false); + this(feedbackToUser, false, false, false); } public String getFeedbackToUser() { @@ -42,6 +46,10 @@ public boolean isShowHelp() { return showHelp; } + public boolean isSwitchSem() { + return switchSem; + } + public boolean isExit() { return exit; } diff --git a/src/main/java/seedu/address/logic/commands/DelModCommand.java b/src/main/java/seedu/address/logic/commands/DelModCommand.java new file mode 100644 index 00000000000..28aef16eed4 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DelModCommand.java @@ -0,0 +1,51 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_MODULE_DOES_NOT_EXIST; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_CODE; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.module.ModuleCode; + +public class DelModCommand extends Command { + + public static final String COMMAND_WORD = "delmod"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes a module from FaculType.\n" + + "Parameters: " + + PREFIX_MODULE_CODE + "MODULE_CODE\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_MODULE_CODE + "CS2103 "; + + public static final String MESSAGE_SUCCESS = "Module deleted: %1$s"; + + private final ModuleCode moduleCode; + + /** + * @param moduleCode module code of the module to be deleted + */ + public DelModCommand(ModuleCode moduleCode) { + requireNonNull(moduleCode); + this.moduleCode = moduleCode; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (!model.hasModuleCode(this.moduleCode)) { + throw new CommandException(String.format(MESSAGE_MODULE_DOES_NOT_EXIST, moduleCode)); + } + + model.deleteModule(this.moduleCode); + return new CommandResult(String.format(MESSAGE_SUCCESS, moduleCode)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DelModCommand // instanceof handles nulls + && moduleCode.equals(((DelModCommand) other).moduleCode)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index 7e36114902f..53708ebafa0 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -1,9 +1,10 @@ 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_DEPARTMENT; 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_OFFICE; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; @@ -19,11 +20,13 @@ import seedu.address.commons.util.CollectionUtil; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; -import seedu.address.model.person.Address; +import seedu.address.model.person.Department; import seedu.address.model.person.Email; import seedu.address.model.person.Name; +import seedu.address.model.person.Office; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Remark; import seedu.address.model.tag.Tag; /** @@ -36,11 +39,13 @@ public class EditCommand extends Command { public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " + "by the index number used in the displayed person list. " + "Existing values will be overwritten by the input values.\n" + + "At least one of the fields must be provided.\n" + "Parameters: INDEX (must be a positive integer) " + "[" + PREFIX_NAME + "NAME] " + "[" + PREFIX_PHONE + "PHONE] " + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " + + "[" + PREFIX_DEPARTMENT + "DEPARTMENT] " + + "[" + PREFIX_OFFICE + "OFFICE] " + "[" + PREFIX_TAG + "TAG]...\n" + "Example: " + COMMAND_WORD + " 1 " + PREFIX_PHONE + "91234567 " @@ -48,7 +53,7 @@ public class EditCommand extends Command { public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %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 person already exists in FaculType."; private final Index index; private final EditPersonDescriptor editPersonDescriptor; @@ -96,10 +101,13 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript 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()); + Department updatedDepartment = editPersonDescriptor.getDepartment().orElse(personToEdit.getDepartment()); + Office updatedOffice = editPersonDescriptor.getOffice().orElse(personToEdit.getOffice()); + Remark updatedRemark = personToEdit.getRemark(); Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + return new Person(updatedName, updatedPhone, updatedEmail, updatedDepartment, updatedOffice, + updatedRemark, updatedTags); } @Override @@ -128,7 +136,8 @@ public static class EditPersonDescriptor { private Name name; private Phone phone; private Email email; - private Address address; + private Department department; + private Office office; private Set tags; public EditPersonDescriptor() {} @@ -141,7 +150,8 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) { setName(toCopy.name); setPhone(toCopy.phone); setEmail(toCopy.email); - setAddress(toCopy.address); + setDepartment(toCopy.department); + setOffice(toCopy.office); setTags(toCopy.tags); } @@ -149,7 +159,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, department, office, tags); } public void setName(Name name) { @@ -176,12 +186,20 @@ public Optional getEmail() { return Optional.ofNullable(email); } - public void setAddress(Address address) { - this.address = address; + public void setDepartment(Department department) { + this.department = department; } - public Optional
getAddress() { - return Optional.ofNullable(address); + public Optional getDepartment() { + return Optional.ofNullable(department); + } + + public void setOffice(Office office) { + this.office = office; + } + + public Optional getOffice() { + return Optional.ofNullable(office); } /** @@ -219,7 +237,8 @@ public boolean equals(Object other) { return getName().equals(e.getName()) && getPhone().equals(e.getPhone()) && getEmail().equals(e.getEmail()) - && getAddress().equals(e.getAddress()) + && getDepartment().equals(e.getDepartment()) + && getOffice().equals(e.getOffice()) && getTags().equals(e.getTags()); } } diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index 3dd85a8ba90..9ad4e560d82 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -11,9 +11,17 @@ public class ExitCommand extends Command { public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; + public static final String MESSAGE_USAGE = "\n" + COMMAND_WORD + ": Exits FaculType\n" + + "Example: " + COMMAND_WORD; + @Override public CommandResult execute(Model model) { - return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); + return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, false, true); } + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ExitCommand); // instanceof handles nulls + } } diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index d6b19b0a0de..9315f9db323 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -1,10 +1,22 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEPARTMENT; +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_OFFICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; import seedu.address.commons.core.Messages; import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.Person; /** * Finds and lists all persons in address book whose name contains any of the argument keywords. @@ -14,29 +26,80 @@ public class FindCommand extends Command { public static final String COMMAND_WORD = "find"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose attributes contain all 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"; + + "At least one of the fields must be provided.\n" + + "Parameters: " + + "[" + PREFIX_NAME + "NAME] " + + "[" + PREFIX_PHONE + "PHONE] " + + "[" + PREFIX_EMAIL + "EMAIL] " + + "[" + PREFIX_DEPARTMENT + "DEPARTMENT] " + + "[" + PREFIX_OFFICE + "OFFICE] " + + "[" + PREFIX_REMARK + "REMARK] " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + + COMMAND_WORD + " " + + PREFIX_NAME + "Alex Yeoh " + + PREFIX_DEPARTMENT + "computing"; + + private final List> predicates; - private final NameContainsKeywordsPredicate predicate; + /** + * Constructs a {@code FindCommand} from the given {@code predicate}. + * + * @param predicate The searching parameter used in finding contacts. + */ + public FindCommand(Predicate predicate) { + List> predicateList = new ArrayList<>(); + predicateList.add(predicate); + this.predicates = predicateList; + } - public FindCommand(NameContainsKeywordsPredicate predicate) { - this.predicate = predicate; + /** + * Constructs a {@code FindCommand} from the given list of {@code predicates}. + * + * @param predicates The searching parameters used in finding contacts. + */ + public FindCommand(List> predicates) { + this.predicates = predicates; + } + + /** + * Returns a composed predicate that represents a short-circuiting logical AND of {@code predicates}. + */ + public static Predicate composePredicates(List> predicates) { + + if (predicates.size() == 1) { + return predicates.get(0); + } + + Predicate composedPredicate = person -> true; + + for (Predicate p : predicates) { + composedPredicate = composedPredicate.and(p); + } + + return composedPredicate; } @Override public CommandResult execute(Model model) { requireNonNull(model); - model.updateFilteredPersonList(predicate); + model.updateFilteredPersonList(composePredicates(predicates)); return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + 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 + || (other instanceof FindCommand // instanceof handles nulls + && predicates.equals(((FindCommand) other).predicates)); // state check + } + + @Override + public String toString() { + return Arrays.toString(predicates.toArray()); } + } diff --git a/src/main/java/seedu/address/logic/commands/FindModCommand.java b/src/main/java/seedu/address/logic/commands/FindModCommand.java new file mode 100644 index 00000000000..64464dd7f0c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FindModCommand.java @@ -0,0 +1,67 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_CODE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_INSTRUCTOR; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_NAME; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.core.Messages; +import seedu.address.model.Model; +import seedu.address.model.module.Module; + +public class FindModCommand extends Command { + + public static final String COMMAND_WORD = "findmod"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds the module whose code, name and instructors' " + + "names contain the specified keywords (case-insensitive) and displays them as a list.\n" + + "At least one of the fields must be provided and each field can have multiple keywords, " + + "except for module code.\n" + + "Parameters: " + + "[" + PREFIX_MODULE_CODE + "MODULE_CODE] " + + "[" + PREFIX_MODULE_NAME + "MODULE_NAME] " + + "[" + PREFIX_MODULE_INSTRUCTOR + "INSTRUCTOR_NAME]\n" + + "Example: " + + COMMAND_WORD + " " + PREFIX_MODULE_CODE + "CS1 " + + PREFIX_MODULE_NAME + "programming methodology " + PREFIX_MODULE_INSTRUCTOR + "Martin"; + + private final List> predicates; + + /** + * Constructor that creates a FindMod Command. + */ + public FindModCommand(List> predicates) { + this.predicates = predicates; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + Predicate composedPredicate = getComposedPredicate(this.predicates); + model.updateFilteredModuleList(composedPredicate); + return new CommandResult( + String.format(Messages.MESSAGE_MODULES_LISTED, model.getFilteredModuleList().size())); + } + + private static Predicate getComposedPredicate(List> predicates) { + assert(predicates != null); + Predicate composedPredicate = module -> true; + for (Predicate predicate : predicates) { + if (predicate == null) { + continue; + } + composedPredicate = composedPredicate.and(predicate); + } + return composedPredicate; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindModCommand // instanceof handles nulls + && predicates.equals(((FindModCommand) other).predicates)); // 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..709e495d5d4 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -9,13 +9,19 @@ public class HelpCommand extends Command { public static final String COMMAND_WORD = "help"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n" + public static final String MESSAGE_USAGE = "\n" + COMMAND_WORD + ": Shows program usage instructions.\n" + "Example: " + COMMAND_WORD; public static final String SHOWING_HELP_MESSAGE = "Opened help window."; @Override public CommandResult execute(Model model) { - return new CommandResult(SHOWING_HELP_MESSAGE, true, false); + return new CommandResult(SHOWING_HELP_MESSAGE, true, false, false); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof HelpCommand); // instanceof handles nulls } } diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 84be6ad2596..71d93a30a6f 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -1,24 +1,35 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_MODULES; 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. + * Lists all persons and modules 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"; + public static final String MESSAGE_USAGE = "\n" + COMMAND_WORD + " : Lists all persons and modules\n" + + "Example: " + COMMAND_WORD; + + public static final String MESSAGE_SUCCESS = "Listed all persons and modules"; @Override public CommandResult execute(Model model) { requireNonNull(model); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateFilteredModuleList(PREDICATE_SHOW_ALL_MODULES); return new CommandResult(MESSAGE_SUCCESS); } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ListCommand); // instanceof handles nulls + } } diff --git a/src/main/java/seedu/address/logic/commands/MclearCommand.java b/src/main/java/seedu/address/logic/commands/MclearCommand.java new file mode 100644 index 00000000000..d76e33950b5 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/MclearCommand.java @@ -0,0 +1,37 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + +public class MclearCommand extends Command { + + public static final String COMMAND_WORD = "mclear"; + + public static final String MESSAGE_SUCCESS = "All modules deleted"; + + public static final String MESSAGE_FAIL = "Module list is already empty"; + + public static final String MESSAGE_USAGE = "\n" + COMMAND_WORD + + ": Clears all the modules from the list.\n" + + "Example: " + COMMAND_WORD; + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.isEmptyModuleList()) { + throw new CommandException(MESSAGE_FAIL); + } else { + model.clearMod(); + return new CommandResult(MESSAGE_SUCCESS); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof MclearCommand); // instanceof handles nulls + } +} diff --git a/src/main/java/seedu/address/logic/commands/MlistCommand.java b/src/main/java/seedu/address/logic/commands/MlistCommand.java new file mode 100644 index 00000000000..1054133d302 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/MlistCommand.java @@ -0,0 +1,31 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_MODULES; + +import seedu.address.model.Model; + +/** + * Lists all modules in the address book to the user. + */ +public class MlistCommand extends Command { + + public static final String COMMAND_WORD = "mlist"; + public static final String MESSAGE_SUCCESS = "Listed all modules"; + public static final String MESSAGE_USAGE = "\n" + COMMAND_WORD + " : Lists all modules\n" + + "Example: " + COMMAND_WORD; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredModuleList(PREDICATE_SHOW_ALL_MODULES); + return new CommandResult(MESSAGE_SUCCESS); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof MlistCommand); // instanceof handles nulls + } +} diff --git a/src/main/java/seedu/address/logic/commands/RemarkCommand.java b/src/main/java/seedu/address/logic/commands/RemarkCommand.java new file mode 100644 index 00000000000..dd1b5bd693d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/RemarkCommand.java @@ -0,0 +1,92 @@ +package seedu.address.logic.commands; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +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; +import seedu.address.model.person.Remark; + +/** + * Changes the remark of an existing person in the address book. + */ +public class RemarkCommand extends Command { + + public static final String COMMAND_WORD = "remark"; + + 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. " + + "Existing remark will be overwritten by the input.\n" + + "Parameters: INDEX (must be a positive integer) " + + PREFIX_REMARK + "[REMARK]\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_REMARK + "Likes to swim."; + + public static final String MESSAGE_ADD_REMARK_SUCCESS = "Added remark to Person: %1$s"; + public static final String MESSAGE_DELETE_REMARK_SUCCESS = "Removed remark from Person: %1$s"; + + private final Index index; + private final Remark remark; + + /** + * @param index of the person in the filtered person list to edit the remark + * @param remark of the person to be updated to + */ + public RemarkCommand(Index index, Remark remark) { + requireAllNonNull(index, remark); + + this.index = index; + this.remark = remark; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person personToEdit = lastShownList.get(index.getZeroBased()); + Person editedPerson = new Person(personToEdit.getName(), personToEdit.getPhone(), personToEdit.getEmail(), + personToEdit.getDepartment(), personToEdit.getOffice(), remark, personToEdit.getTags()); + + model.setPerson(personToEdit, editedPerson); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + + return new CommandResult(generateSuccessMessage(editedPerson)); + } + + /** + * Generates a command execution success message based on whether the remark is added to or removed from + * {@code personToEdit}. + */ + private String generateSuccessMessage(Person personToEdit) { + String message = !remark.value.isEmpty() ? MESSAGE_ADD_REMARK_SUCCESS : MESSAGE_DELETE_REMARK_SUCCESS; + return String.format(message, personToEdit); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof RemarkCommand)) { + return false; + } + + // state check + RemarkCommand e = (RemarkCommand) other; + return index.equals(e.index) + && remark.equals(e.remark); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ResetCommand.java b/src/main/java/seedu/address/logic/commands/ResetCommand.java new file mode 100644 index 00000000000..85287007808 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ResetCommand.java @@ -0,0 +1,31 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.model.AddressBook; +import seedu.address.model.Model; + +/** + * Clears the address book. + */ +public class ResetCommand extends Command { + + public static final String COMMAND_WORD = "reset"; + public static final String MESSAGE_SUCCESS = "FaculType has been reset!"; + public static final String MESSAGE_USAGE = "\n" + COMMAND_WORD + " : Resets FaculType\n" + + "Example: " + COMMAND_WORD; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.setAddressBook(new AddressBook()); + return new CommandResult(MESSAGE_SUCCESS); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ResetCommand); // instanceof handles nulls + } +} diff --git a/src/main/java/seedu/address/logic/commands/SwitchCommand.java b/src/main/java/seedu/address/logic/commands/SwitchCommand.java new file mode 100644 index 00000000000..bf017a86bb8 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SwitchCommand.java @@ -0,0 +1,36 @@ +package seedu.address.logic.commands; + +import seedu.address.model.Model; + +/** + * Switches the active module list in FaculType. + */ +public class SwitchCommand extends Command { + + public static final String COMMAND_WORD = "switch"; + + public static final String MESSAGE_USAGE = "\n" + COMMAND_WORD + ": Switches the active module list in FaculType." + + "\nExample: " + COMMAND_WORD; + + public static final String MESSAGE_SWITCH_SUCCESS = "Switched active module list to Semester %s"; + + @Override + public CommandResult execute(Model model) { + model.switchModuleList(); + + return new CommandResult( + String.format(MESSAGE_SWITCH_SUCCESS, model.getSemester()), + false, true, false); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + return other instanceof SwitchCommand; + } +} diff --git a/src/main/java/seedu/address/logic/commands/UnassignCommand.java b/src/main/java/seedu/address/logic/commands/UnassignCommand.java new file mode 100644 index 00000000000..45d61f12d8f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/UnassignCommand.java @@ -0,0 +1,113 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INSTRUCTOR_IS_NOT_ASSIGNED; +import static seedu.address.commons.core.Messages.MESSAGE_MODULE_DOES_NOT_EXIST; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_CODE; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.person.Person; + +public class UnassignCommand extends Command { + + public static final String COMMAND_WORD = "unassign"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Unassigns an instructor from one or more modules.\n" + + "If module codes are provided, it will unassign the instructor " + + "from all module codes provided.\n" + + "Otherwise, it will unassign the instructor from all modules he/she teaches.\n" + + "Parameters: INDEX (must be a positive integer) " + + PREFIX_MODULE_CODE + "[MODULE CODE]\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_MODULE_CODE + "CS2103" + + " or " + COMMAND_WORD + " 1 m/"; + + public static final String MESSAGE_UNASSIGNMENT_SUCCESS = "Unassigned %1$s from %2$s"; + + private final Index index; + private final Set moduleCodes; + + /** + * @param index of the person in the filtered person list to be unassigned + * @param moduleCodes of modules the person would be unassigned from + */ + public UnassignCommand(Index index, Set moduleCodes) { + requireAllNonNull(index, moduleCodes); + + this.index = index; + this.moduleCodes = moduleCodes; + } + + /** + * @param index of the person in the filtered person list to be unassigned + */ + public UnassignCommand(Index index) { + requireAllNonNull(index); + + this.index = index; + this.moduleCodes = new HashSet<>(); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + List lastShownList = model.getFilteredPersonList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person instructor = lastShownList.get(index.getZeroBased()); + StringBuilder moduleString = new StringBuilder(); + + if (moduleCodes.isEmpty()) { + model.unassignInstructorFromAll(instructor); + moduleString.append("all modules"); + } else { + for (ModuleCode moduleCode : moduleCodes) { + if (!model.hasModuleCode(moduleCode)) { + throw new CommandException(String.format(MESSAGE_MODULE_DOES_NOT_EXIST, moduleCode)); + } + } + + for (ModuleCode moduleCode : moduleCodes) { + if (!model.moduleCodeHasInstructor(moduleCode, instructor)) { + throw new CommandException(String.format(MESSAGE_INSTRUCTOR_IS_NOT_ASSIGNED, + instructor.getName(), moduleCode)); + } + } + + for (ModuleCode moduleCode : moduleCodes) { + model.unassignInstructor(instructor, moduleCode); + moduleString.append(moduleCode).append(", "); + } + + assert moduleString.length() > 2; + + moduleString.delete(moduleString.length() - 2, moduleString.length()); + } + + return new CommandResult(String.format(MESSAGE_UNASSIGNMENT_SUCCESS, + instructor.getName(), moduleString)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UnassignCommand // instanceof handles nulls + && index.equals(((UnassignCommand) other).index) + && moduleCodes.equals(((UnassignCommand) other).moduleCodes)); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/UnassignallCommand.java b/src/main/java/seedu/address/logic/commands/UnassignallCommand.java new file mode 100644 index 00000000000..47575074307 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/UnassignallCommand.java @@ -0,0 +1,31 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.model.Model; + + +public class UnassignallCommand extends Command { + + public static final String COMMAND_WORD = "unassignall"; + + public static final String MESSAGE_USAGE = "\n" + COMMAND_WORD + + ": Unassigns all instructors from all modules.\n" + + "Example: " + COMMAND_WORD; + + public static final String MESSAGE_SUCCESS = "Unassigned all instructors from all modules " + + "in the active semester"; + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.unassignAllInstructors(); + return new CommandResult(MESSAGE_SUCCESS); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UnassignallCommand); // instanceof handles nulls + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 3b8bfa035e8..106d9340047 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -1,22 +1,26 @@ package seedu.address.logic.parser; 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.ArgumentMultimap.arePrefixesPresent; +import static seedu.address.logic.parser.ArgumentMultimap.checkDuplicatePrefix; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEPARTMENT; 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_OFFICE; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import java.util.Set; -import java.util.stream.Stream; import seedu.address.logic.commands.AddCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; +import seedu.address.model.person.Department; import seedu.address.model.person.Email; import seedu.address.model.person.Name; +import seedu.address.model.person.Office; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Remark; import seedu.address.model.tag.Tag; /** @@ -31,30 +35,27 @@ public class AddCommandParser implements Parser { */ public AddCommand parse(String args) throws ParseException { ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, + PREFIX_EMAIL, PREFIX_DEPARTMENT, PREFIX_OFFICE, PREFIX_TAG); - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_DEPARTMENT, + PREFIX_OFFICE, PREFIX_PHONE, PREFIX_EMAIL) || !argMultimap.getPreamble().isEmpty()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); } + checkDuplicatePrefix(argMultimap, PREFIX_NAME, PREFIX_DEPARTMENT, PREFIX_OFFICE, PREFIX_PHONE, PREFIX_EMAIL); + 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()); + Department department = ParserUtil.parseDepartment(argMultimap.getValue(PREFIX_DEPARTMENT).get()); + Office office = ParserUtil.parseOffice(argMultimap.getValue(PREFIX_OFFICE).get()); + Remark remark = new Remark(""); Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - Person person = new Person(name, phone, email, address, tagList); + Person person = new Person(name, phone, email, department, office, remark, tagList); return new AddCommand(person); } - - /** - * 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/AddModCommandParser.java b/src/main/java/seedu/address/logic/parser/AddModCommandParser.java new file mode 100644 index 00000000000..06ef32e3d3d --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddModCommandParser.java @@ -0,0 +1,46 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.ArgumentMultimap.arePrefixesPresent; +import static seedu.address.logic.parser.ArgumentMultimap.checkDuplicatePrefix; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_CODE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_NAME; + +import seedu.address.logic.commands.AddModCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.module.ModuleName; + +/** + * Parses input arguments and creates a new AddCommand object + */ +public class AddModCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddCommand + * and returns an AddCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public AddModCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_MODULE_CODE, PREFIX_MODULE_NAME); + + if (!arePrefixesPresent(argMultimap, PREFIX_MODULE_CODE, PREFIX_MODULE_NAME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddModCommand.MESSAGE_USAGE)); + } + + checkDuplicatePrefix(argMultimap, PREFIX_MODULE_CODE, PREFIX_MODULE_NAME); + + ModuleCode moduleCode = ParserUtil.parseModuleCode(argMultimap.getValue(PREFIX_MODULE_CODE).get()); + ModuleName moduleName = ParserUtil.parseModuleName(argMultimap.getValue(PREFIX_MODULE_NAME).get()); + + + Module module = new Module(moduleCode, moduleName); + + return new AddModCommand(module); + } +} + diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index 1e466792b46..91faf94f047 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -7,14 +7,26 @@ import java.util.regex.Pattern; import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; +import seedu.address.logic.commands.AddModCommand; +import seedu.address.logic.commands.AssignCommand; +import seedu.address.logic.commands.CclearCommand; +import seedu.address.logic.commands.ClistCommand; import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.DelModCommand; 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.FindModCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.MclearCommand; +import seedu.address.logic.commands.MlistCommand; +import seedu.address.logic.commands.RemarkCommand; +import seedu.address.logic.commands.ResetCommand; +import seedu.address.logic.commands.SwitchCommand; +import seedu.address.logic.commands.UnassignCommand; +import seedu.address.logic.commands.UnassignallCommand; import seedu.address.logic.parser.exceptions.ParseException; /** @@ -53,24 +65,52 @@ public Command parseCommand(String userInput) throws ParseException { case DeleteCommand.COMMAND_WORD: return new DeleteCommandParser().parse(arguments); - case ClearCommand.COMMAND_WORD: - return new ClearCommand(); + case RemarkCommand.COMMAND_WORD: + return new RemarkCommandParser().parse(arguments); case FindCommand.COMMAND_WORD: return new FindCommandParser().parse(arguments); - case ListCommand.COMMAND_WORD: - return new ListCommand(); + case AddModCommand.COMMAND_WORD: + return new AddModCommandParser().parse(arguments); - case ExitCommand.COMMAND_WORD: - return new ExitCommand(); + case DelModCommand.COMMAND_WORD: + return new DelModCommandParser().parse(arguments); + + case FindModCommand.COMMAND_WORD: + return new FindModCommandParser().parse(arguments); + + case AssignCommand.COMMAND_WORD: + return new AssignCommandParser().parse(arguments); + + case UnassignCommand.COMMAND_WORD: + return new UnassignCommandParser().parse(arguments); + + case SwitchCommand.COMMAND_WORD: + + case ResetCommand.COMMAND_WORD: case HelpCommand.COMMAND_WORD: - return new HelpCommand(); + + case ExitCommand.COMMAND_WORD: + + case UnassignallCommand.COMMAND_WORD: + + case MlistCommand.COMMAND_WORD: + + case ClistCommand.COMMAND_WORD: + + case MclearCommand.COMMAND_WORD: + + case CclearCommand.COMMAND_WORD: + + case ListCommand.COMMAND_WORD: + return new OneWordCommandParser().parse(userInput); default: throw new ParseException(MESSAGE_UNKNOWN_COMMAND); } } + } diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java index 954c8e18f8e..d999e73de0d 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java +++ b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java @@ -1,10 +1,15 @@ package seedu.address.logic.parser; +import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_PREFIX; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Stream; + +import seedu.address.logic.parser.exceptions.ParseException; /** * Stores mapping of prefixes to their respective arguments. @@ -57,4 +62,34 @@ public List getAllValues(Prefix prefix) { public String getPreamble() { return getValue(new Prefix("")).orElse(""); } + + //@author erinmayg + /** + * Throws a {@code ParseException} if there is a duplicate prefix. + */ + public static void checkDuplicatePrefix(ArgumentMultimap argumentMultimap, Prefix... prefixes) + throws ParseException { + for (Prefix p : prefixes) { + if (argumentMultimap.getAllValues(p).size() > 1) { + throw new ParseException(String.format(MESSAGE_DUPLICATE_PREFIX, p)); + } + } + } + //@author erinmayg + + /** + * Returns true if one of the prefixes has an {@code Optional} value in the given + * {@code ArgumentMultimap}. + */ + public static boolean areAnyPrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + public 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/AssignCommandParser.java b/src/main/java/seedu/address/logic/parser/AssignCommandParser.java new file mode 100644 index 00000000000..5d1cda3a728 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AssignCommandParser.java @@ -0,0 +1,48 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_CODE; + +import java.util.Set; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.AssignCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.module.ModuleCode; + +public class AssignCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AssignCommand + * and returns an AssignCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AssignCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, + PREFIX_MODULE_CODE); + + Index index; + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AssignCommand.MESSAGE_USAGE), ive); + } + + assert index != null; + + Set moduleCodes = ParserUtil.parseModuleCodes(argMultimap.getAllValues(PREFIX_MODULE_CODE)); + + if (moduleCodes.size() == 0) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AssignCommand.MESSAGE_USAGE)); + } + + assert moduleCodes != null; + + return new AssignCommand(index, moduleCodes); + } +} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf119..ee715b1f3c4 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -9,7 +9,11 @@ public class CliSyntax { public static final Prefix PREFIX_NAME = new Prefix("n/"); public static final Prefix PREFIX_PHONE = new Prefix("p/"); public static final Prefix PREFIX_EMAIL = new Prefix("e/"); - public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); + public static final Prefix PREFIX_DEPARTMENT = new Prefix("d/"); + public static final Prefix PREFIX_OFFICE = new Prefix("o/"); + public static final Prefix PREFIX_REMARK = new Prefix("r/"); public static final Prefix PREFIX_TAG = new Prefix("t/"); - + public static final Prefix PREFIX_MODULE_CODE = new Prefix("m/"); + public static final Prefix PREFIX_MODULE_NAME = new Prefix("n/"); + public static final Prefix PREFIX_MODULE_INSTRUCTOR = new Prefix("i/"); } diff --git a/src/main/java/seedu/address/logic/parser/DelModCommandParser.java b/src/main/java/seedu/address/logic/parser/DelModCommandParser.java new file mode 100644 index 00000000000..52c4a89c5bc --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DelModCommandParser.java @@ -0,0 +1,42 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.ArgumentMultimap.arePrefixesPresent; +import static seedu.address.logic.parser.ArgumentMultimap.checkDuplicatePrefix; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_CODE; + +import seedu.address.logic.commands.DelModCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.module.ModuleCode; + +/** + * Parses input arguments and creates a new DeleteCommand object + */ +public class DelModCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DelModCommand + * and returns a DelModCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DelModCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_MODULE_CODE); + + if (!arePrefixesPresent(argMultimap, PREFIX_MODULE_CODE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DelModCommand.MESSAGE_USAGE)); + } + + checkDuplicatePrefix(argMultimap, PREFIX_MODULE_CODE); + + ModuleCode moduleCode = ParserUtil.parseModuleCode(argMultimap.getValue(PREFIX_MODULE_CODE).get()); + + try { + return new DelModCommand(moduleCode); + } catch (IllegalArgumentException e) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DelModCommand.MESSAGE_USAGE), e); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index 845644b7dea..778bca73fc2 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -2,9 +2,11 @@ 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.ArgumentMultimap.checkDuplicatePrefix; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEPARTMENT; 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_OFFICE; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; @@ -32,7 +34,10 @@ public class EditCommandParser implements Parser { public EditCommand parse(String args) throws ParseException { requireNonNull(args); ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_DEPARTMENT, PREFIX_OFFICE, PREFIX_TAG); + + checkDuplicatePrefix(argMultimap, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_DEPARTMENT, PREFIX_OFFICE); Index index; @@ -52,8 +57,13 @@ public EditCommand parse(String args) throws ParseException { if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { editPersonDescriptor.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_DEPARTMENT).isPresent()) { + editPersonDescriptor.setDepartment( + ParserUtil.parseDepartment(argMultimap.getValue(PREFIX_DEPARTMENT).get())); + } + if (argMultimap.getValue(PREFIX_OFFICE).isPresent()) { + editPersonDescriptor.setOffice( + ParserUtil.parseOffice(argMultimap.getValue(PREFIX_OFFICE).get())); } parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); @@ -78,5 +88,4 @@ private Optional> parseTagsForEdit(Collection tags) throws Pars Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; return Optional.of(ParserUtil.parseTags(tagSet)); } - } diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java index 4fb71f23103..61a658f8c12 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -1,33 +1,120 @@ package seedu.address.logic.parser; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_NAME_KEYWORD; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_PHONE_KEYWORD; +import static seedu.address.logic.parser.ArgumentMultimap.areAnyPrefixesPresent; +import static seedu.address.logic.parser.ArgumentMultimap.checkDuplicatePrefix; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DEPARTMENT; +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_OFFICE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Stream; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.Person; +import seedu.address.model.person.predicates.DepartmentContainsKeywordsPredicate; +import seedu.address.model.person.predicates.EmailContainsKeywordsPredicate; +import seedu.address.model.person.predicates.NameContainsKeywordsPredicate; +import seedu.address.model.person.predicates.OfficeContainsKeywordsPredicate; +import seedu.address.model.person.predicates.PhoneContainsKeywordsPredicate; +import seedu.address.model.person.predicates.RemarkContainsKeywordsPredicate; +import seedu.address.model.person.predicates.TagContainsKeywordsPredicate; /** * Parses input arguments and creates a new FindCommand object */ public class FindCommandParser implements Parser { + /** + * Name keywords should be alphanumeric and the first character should not be a whitespace. + */ + public static final String NAME_VALIDATION_REGEX = "[\\p{Alnum}]+"; + + /** + * Phone keywords should be numeric and the first character should not be a whitespace. + */ + public static final String PHONE_VALIDATION_REGEX = "\\p{Digit}+"; + /** * 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)); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_DEPARTMENT, PREFIX_OFFICE, PREFIX_REMARK, PREFIX_TAG); + + List> predicates = new ArrayList<>(); + + if (!areAnyPrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_DEPARTMENT, PREFIX_OFFICE, PREFIX_REMARK, PREFIX_TAG) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); } - String[] nameKeywords = trimmedArgs.split("\\s+"); + checkDuplicatePrefix(argMultimap, PREFIX_NAME, PREFIX_DEPARTMENT, PREFIX_OFFICE, + PREFIX_REMARK, PREFIX_TAG); + + getKeywords(argMultimap, PREFIX_NAME) + .ifPresent(k -> predicates.add(new NameContainsKeywordsPredicate(Arrays.asList(k)))); + getKeywords(argMultimap, PREFIX_PHONE) + .ifPresent(k -> predicates.add(new PhoneContainsKeywordsPredicate(Arrays.asList(k)))); + getKeywords(argMultimap, PREFIX_EMAIL) + .ifPresent(k -> predicates.add(new EmailContainsKeywordsPredicate(Arrays.asList(k)))); + getKeywords(argMultimap, PREFIX_DEPARTMENT) + .ifPresent(k -> predicates.add(new DepartmentContainsKeywordsPredicate(Arrays.asList(k)))); + getKeywords(argMultimap, PREFIX_OFFICE) + .ifPresent(k -> predicates.add(new OfficeContainsKeywordsPredicate(Arrays.asList(k)))); + getKeywords(argMultimap, PREFIX_REMARK) + .ifPresent(k -> predicates.add(new RemarkContainsKeywordsPredicate(Arrays.asList(k)))); + getKeywords(argMultimap, PREFIX_TAG) + .ifPresent(k -> predicates.add(new TagContainsKeywordsPredicate(Arrays.asList(k)))); + + assert (!predicates.isEmpty()); + + return new FindCommand(predicates); + } + + /** + * Throws a {@code ParseException} if keyword does not match valid attribute format. + */ + private void checkKeywords(String[] keywords, Prefix prefix) throws ParseException { + if (prefix.equals(PREFIX_PHONE) + && Stream.of(keywords).anyMatch(k -> !k.matches(PHONE_VALIDATION_REGEX))) { + throw new ParseException(MESSAGE_INVALID_PHONE_KEYWORD); + } + + if (prefix.equals(PREFIX_NAME) + && Stream.of(keywords).anyMatch(k -> !k.matches(NAME_VALIDATION_REGEX))) { + throw new ParseException(MESSAGE_INVALID_NAME_KEYWORD); + } + } + + /** + * Returns keywords stored as the value of {@code prefix}. + */ + private Optional getKeywords(ArgumentMultimap argMultimap, Prefix prefix) throws ParseException { + if (argMultimap.getValue(prefix).isEmpty()) { + return Optional.empty(); + } - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + String[] keywords; + keywords = ParserUtil.parseString(argMultimap.getValue(prefix).get()).split("\\s+"); + checkKeywords(keywords, prefix); + return Optional.of(keywords); } } diff --git a/src/main/java/seedu/address/logic/parser/FindModCommandParser.java b/src/main/java/seedu/address/logic/parser/FindModCommandParser.java new file mode 100644 index 00000000000..c367054c821 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindModCommandParser.java @@ -0,0 +1,85 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_MODULE_CODE_KEYWORD; +import static seedu.address.logic.parser.ArgumentMultimap.checkDuplicatePrefix; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_CODE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_INSTRUCTOR; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_NAME; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; + +import seedu.address.logic.commands.FindModCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.module.Module; +import seedu.address.model.module.predicates.ModuleCodeContainsKeywordsPredicate; +import seedu.address.model.module.predicates.ModuleInstructorsContainsKeywordsPredicate; +import seedu.address.model.module.predicates.ModuleNameContainsKeywordsPredicate; + +public class FindModCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FindModCommand + * and returns a FindModCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public FindModCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_MODULE_CODE, PREFIX_MODULE_NAME, PREFIX_MODULE_INSTRUCTOR); + + List> predicates = new ArrayList<>(); + + if (!ArgumentMultimap.areAnyPrefixesPresent(argMultimap, PREFIX_MODULE_CODE, PREFIX_MODULE_NAME, + PREFIX_MODULE_INSTRUCTOR) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindModCommand.MESSAGE_USAGE)); + } + + checkDuplicatePrefix(argMultimap, PREFIX_MODULE_CODE, PREFIX_MODULE_NAME, PREFIX_MODULE_INSTRUCTOR); + + getKeyword(argMultimap, PREFIX_MODULE_CODE) + .ifPresent(k -> predicates.add(new ModuleCodeContainsKeywordsPredicate(k))); + getKeywords(argMultimap, PREFIX_MODULE_NAME) + .ifPresent(k -> predicates.add(new ModuleNameContainsKeywordsPredicate(Arrays.asList(k)))); + getKeywords(argMultimap, PREFIX_MODULE_INSTRUCTOR) + .ifPresent(k -> predicates.add(new ModuleInstructorsContainsKeywordsPredicate(Arrays.asList(k)))); + + assert (!predicates.isEmpty()); + return new FindModCommand(predicates); + } + + /** + * Returns keywords stored as the value of {@code prefix}. + */ + private Optional getKeywords(ArgumentMultimap argMultimap, Prefix prefix) throws ParseException { + if (argMultimap.getValue(prefix).isEmpty()) { + return Optional.empty(); + } + + String[] keywords; + keywords = ParserUtil.parseString(argMultimap.getValue(prefix).get()).split("\\s+"); + return Optional.of(keywords); + } + + /** + * Returns the keyword stored as the value of {@code prefix}. + */ + private Optional getKeyword(ArgumentMultimap argMultimap, Prefix prefix) throws ParseException { + if (argMultimap.getValue(prefix).isEmpty()) { + return Optional.empty(); + } + + String[] keywords = ParserUtil.parseString(argMultimap.getValue(prefix).get()).split("\\s+"); + + if (keywords.length != 1) { + throw new ParseException(MESSAGE_INVALID_MODULE_CODE_KEYWORD); + } + + return Optional.of(keywords[0]); + } +} diff --git a/src/main/java/seedu/address/logic/parser/OneWordCommandParser.java b/src/main/java/seedu/address/logic/parser/OneWordCommandParser.java new file mode 100644 index 00000000000..b0d11f3b3c7 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/OneWordCommandParser.java @@ -0,0 +1,146 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_NON_EMPTY_ARGUMENT; +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.CclearCommand; +import seedu.address.logic.commands.ClistCommand; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.HelpCommand; +import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.MclearCommand; +import seedu.address.logic.commands.MlistCommand; +import seedu.address.logic.commands.ResetCommand; +import seedu.address.logic.commands.SwitchCommand; +import seedu.address.logic.commands.UnassignallCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +public class OneWordCommandParser implements Parser { + + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + + /** + * Parses the given {@code String} of arguments in the context of the ListCommand + * and returns an ListCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public Command parse(String userInput) throws ParseException { + requireNonNull(userInput); + + 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 ResetCommand.COMMAND_WORD: + if (!arguments.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_NON_EMPTY_ARGUMENT, ResetCommand.COMMAND_WORD) + + ResetCommand.MESSAGE_USAGE); + } + + return new ResetCommand(); + + case SwitchCommand.COMMAND_WORD: + if (!arguments.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_NON_EMPTY_ARGUMENT, SwitchCommand.COMMAND_WORD) + + SwitchCommand.MESSAGE_USAGE); + } + + return new SwitchCommand(); + + case ListCommand.COMMAND_WORD: + if (!arguments.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_NON_EMPTY_ARGUMENT, ListCommand.COMMAND_WORD) + + ListCommand.MESSAGE_USAGE); + } + + return new ListCommand(); + + case CclearCommand.COMMAND_WORD: + if (!arguments.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_NON_EMPTY_ARGUMENT, CclearCommand.COMMAND_WORD) + + CclearCommand.MESSAGE_USAGE); + } + + return new CclearCommand(); + + case MclearCommand.COMMAND_WORD: + if (!arguments.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_NON_EMPTY_ARGUMENT, MclearCommand.COMMAND_WORD) + + MclearCommand.MESSAGE_USAGE); + } + + return new MclearCommand(); + + case ClistCommand.COMMAND_WORD: + if (!arguments.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_NON_EMPTY_ARGUMENT, ClistCommand.COMMAND_WORD) + + ClistCommand.MESSAGE_USAGE); + } + + return new ClistCommand(); + + case MlistCommand.COMMAND_WORD: + if (!arguments.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_NON_EMPTY_ARGUMENT, MlistCommand.COMMAND_WORD) + + MlistCommand.MESSAGE_USAGE); + } + + return new MlistCommand(); + + case UnassignallCommand.COMMAND_WORD: + if (!arguments.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_NON_EMPTY_ARGUMENT, UnassignallCommand.COMMAND_WORD) + + UnassignallCommand.MESSAGE_USAGE); + } + + return new UnassignallCommand(); + + case HelpCommand.COMMAND_WORD: + if (!arguments.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_NON_EMPTY_ARGUMENT, HelpCommand.COMMAND_WORD) + + HelpCommand.MESSAGE_USAGE); + } + + return new HelpCommand(); + + case ExitCommand.COMMAND_WORD: + if (!arguments.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_NON_EMPTY_ARGUMENT, ExitCommand.COMMAND_WORD) + + ExitCommand.MESSAGE_USAGE); + } + + return new ExitCommand(); + + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + + + } +} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..99b29680530 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -1,6 +1,8 @@ package seedu.address.logic.parser; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_DUPLICATE_MODULE_CODE; +import static seedu.address.commons.core.Messages.MESSAGE_EMPTY_KEYWORD; import java.util.Collection; import java.util.HashSet; @@ -9,9 +11,12 @@ 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.module.ModuleCode; +import seedu.address.model.module.ModuleName; +import seedu.address.model.person.Department; import seedu.address.model.person.Email; import seedu.address.model.person.Name; +import seedu.address.model.person.Office; import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; @@ -25,6 +30,7 @@ public class ParserUtil { /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be * trimmed. + * * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). */ public static Index parseIndex(String oneBasedIndex) throws ParseException { @@ -66,18 +72,33 @@ public static Phone parsePhone(String phone) throws ParseException { } /** - * Parses a {@code String address} into an {@code Address}. + * Parses a {@code String department} into an {@code Department}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code department} is invalid. + */ + public static Department parseDepartment(String department) throws ParseException { + requireNonNull(department); + String trimmedDepartment = department.trim(); + if (!Department.isValidDepartment(trimmedDepartment)) { + throw new ParseException(Department.MESSAGE_CONSTRAINTS); + } + return new Department(trimmedDepartment); + } + + /** + * Parses a {@code String office} into an {@code Office}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code address} is invalid. + * @throws ParseException if the given {@code Office} is invalid. */ - public static Address parseAddress(String address) throws ParseException { - requireNonNull(address); - String trimmedAddress = address.trim(); - if (!Address.isValidAddress(trimmedAddress)) { - throw new ParseException(Address.MESSAGE_CONSTRAINTS); + public static Office parseOffice(String office) throws ParseException { + requireNonNull(office); + String trimmedOffice = office.trim(); + if (!Office.isValidOffice(trimmedOffice)) { + throw new ParseException(Office.MESSAGE_CONSTRAINTS); } - return new Address(trimmedAddress); + return new Office(trimmedOffice); } /** @@ -121,4 +142,61 @@ public static Set parseTags(Collection tags) throws ParseException } return tagSet; } + + /** + * Parses a {@code String moduleCode} into a {@code ModuleCode}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code moduleCode} is invalid. + */ + public static ModuleCode parseModuleCode(String moduleCode) throws ParseException { + requireNonNull(moduleCode); + String trimmedModuleCode = moduleCode.trim(); + if (!ModuleCode.isValidCode(trimmedModuleCode)) { + throw new ParseException(ModuleCode.MESSAGE_CONSTRAINTS); + } + return new ModuleCode(trimmedModuleCode); + } + + /** + * Parses {@code Collection moduleCodes} into a {@code Set}. + * Module codes must be distinct. + */ + public static Set parseModuleCodes(Collection moduleCodes) throws ParseException { + requireNonNull(moduleCodes); + final Set codeSet = new HashSet<>(); + for (String code : moduleCodes) { + if (codeSet.contains(parseModuleCode(code))) { + throw new ParseException(String.format(MESSAGE_DUPLICATE_MODULE_CODE, code)); + } + codeSet.add(parseModuleCode(code)); + } + return codeSet; + } + + /** + * Parses a {@code String moduleName} into a {@code ModuleName}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code moduleName} is invalid. + */ + public static ModuleName parseModuleName(String moduleName) throws ParseException { + requireNonNull(moduleName); + String trimmedModuleName = moduleName.trim(); + if (!ModuleName.isValidName(trimmedModuleName)) { + throw new ParseException(ModuleName.MESSAGE_CONSTRAINTS); + } + return new ModuleName(trimmedModuleName); + } + /** + * + */ + public static String parseString(String string) throws ParseException { + requireNonNull(string); + String trimmedString = string.trim(); + if (trimmedString.isEmpty() || trimmedString.isBlank()) { + throw new ParseException(MESSAGE_EMPTY_KEYWORD); + } + return trimmedString; + } } diff --git a/src/main/java/seedu/address/logic/parser/RemarkCommandParser.java b/src/main/java/seedu/address/logic/parser/RemarkCommandParser.java new file mode 100644 index 00000000000..d2a9a8d586b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/RemarkCommandParser.java @@ -0,0 +1,49 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.ArgumentMultimap.checkDuplicatePrefix; +import static seedu.address.logic.parser.CliSyntax.PREFIX_REMARK; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.RemarkCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Remark; + +public class RemarkCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the RemarkCommand + * and returns an RemarkCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public RemarkCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, + PREFIX_REMARK); + + if (argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, RemarkCommand.MESSAGE_USAGE)); + } + + checkDuplicatePrefix(argMultimap, PREFIX_REMARK); + + Index index; + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RemarkCommand.MESSAGE_USAGE), ive); + } + + if (argMultimap.getValue(PREFIX_REMARK).isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + RemarkCommand.MESSAGE_USAGE)); + } + + Remark remark = new Remark(argMultimap.getValue(PREFIX_REMARK).orElse("")); + + return new RemarkCommand(index, remark); + } +} diff --git a/src/main/java/seedu/address/logic/parser/UnassignCommandParser.java b/src/main/java/seedu/address/logic/parser/UnassignCommandParser.java new file mode 100644 index 00000000000..f1b4f854d98 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/UnassignCommandParser.java @@ -0,0 +1,52 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE_CODE; + +import java.util.Set; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.UnassignCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.module.ModuleCode; + +public class UnassignCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the UnassignCommand + * and returns an UnassignCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public UnassignCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, + PREFIX_MODULE_CODE); + + if (argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnassignCommand.MESSAGE_USAGE)); + } + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + UnassignCommand.MESSAGE_USAGE), ive); + } + + if (argMultimap.getValue(PREFIX_MODULE_CODE).isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + UnassignCommand.MESSAGE_USAGE)); + } + + + if (argMultimap.getValue(PREFIX_MODULE_CODE).get().isEmpty()) { + return new UnassignCommand(index); + } else { + Set moduleCodes = ParserUtil.parseModuleCodes(argMultimap.getAllValues(PREFIX_MODULE_CODE)); + return new UnassignCommand(index, moduleCodes); + } + } +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 1a943a0781a..c8b0a099718 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -1,10 +1,16 @@ package seedu.address.model; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.util.List; +import java.util.Objects; import javafx.collections.ObservableList; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.module.UniqueModuleList; import seedu.address.model.person.Person; import seedu.address.model.person.UniquePersonList; @@ -15,6 +21,9 @@ public class AddressBook implements ReadOnlyAddressBook { private final UniquePersonList persons; + private UniqueModuleList activeModules; + private final UniqueModuleList semOneModules; + private final UniqueModuleList semTwoModules; /* * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication @@ -25,6 +34,9 @@ public class AddressBook implements ReadOnlyAddressBook { */ { persons = new UniquePersonList(); + semOneModules = new UniqueModuleList(); + semTwoModules = new UniqueModuleList(); + activeModules = semOneModules; } public AddressBook() {} @@ -47,6 +59,30 @@ public void setPersons(List persons) { this.persons.setPersons(persons); } + /** + * Replaces the contents of the active module list with {@code modules}. + * {@code modules} must not contain duplicate modules. + */ + public void setActiveModules(List modules) { + this.activeModules.setModules(modules); + } + + /** + * Replaces the contents of the semester one list with {@code modules}. + * {@code modules} must not contain duplicate modules. + */ + public void setSemOneModules(List modules) { + this.semOneModules.setModules(modules); + } + + /** + * Replaces the contents of the semester two list with {@code modules}. + * {@code modules} must not contain duplicate modules. + */ + public void setSemTwoModules(List modules) { + this.semTwoModules.setModules(modules); + } + /** * Resets the existing data of this {@code AddressBook} with {@code newData}. */ @@ -54,10 +90,12 @@ public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); setPersons(newData.getPersonList()); + setSemOneModules(newData.getSemOneModuleList()); + setSemTwoModules(newData.getSemTwoModuleList()); } - //// person-level operations + //// person-level operations /** * Returns true if a person with the same identity as {@code person} exists in the address book. */ @@ -81,7 +119,7 @@ public void addPerson(Person p) { */ public void setPerson(Person target, Person editedPerson) { requireNonNull(editedPerson); - + reassignEditedInstructor(target, editedPerson); persons.setPerson(target, editedPerson); } @@ -91,6 +129,166 @@ public void setPerson(Person target, Person editedPerson) { */ public void removePerson(Person key) { persons.remove(key); + deleteInstructorAssignments(key); + } + + /** + * Removes all the contacts from the list and unassigns all contacts from all modules. + */ + public void clearContacts() { + semOneModules.unassignAllInstructors(); + semTwoModules.unassignAllInstructors(); + persons.clearAll(); + } + + /** + * Removes the module with the specified {@code moduleCode} from this {@code AddressBook}. + * Module with the {@code moduleCode} must exist in the address book. + */ + public void removeModule(ModuleCode moduleCode) { + activeModules.removeModuleWithCode(moduleCode); + } + + /** + * Removes {@code key} from this {@code AddressBook}. + * {@code key} must exist in the address book. + */ + public void removeModule(Module key) { + activeModules.remove(key); + } + + /** + * Removes all the modules from the list. + */ + public void clearMod() { + activeModules.clearAll(); + } + + //// module-level operations + /** + * Returns true if a module with the same identity as {@code module} exists in the active module list. + */ + public boolean hasModule(Module module) { + requireNonNull(module); + return activeModules.contains(module); + } + + /** + * Returns true if a module with the same identity as {@code module} exists in the semester one module list. + */ + public boolean hasSemOneModule(Module module) { + requireNonNull(module); + return semOneModules.contains(module); + } + + /** + * Returns true if a module with the same identity as {@code module} exists in the semester two module list. + */ + public boolean hasSemTwoModule(Module module) { + requireNonNull(module); + return semTwoModules.contains(module); + } + + /** + * Returns true if a module with the same module code as {@code moduleCode} exists in the active module list. + */ + public boolean hasModuleCode(ModuleCode moduleCode) { + requireNonNull(moduleCode); + return activeModules.containsModuleCode(moduleCode); + } + + /** + * Adds a module to the address book. + * The module must not already exist in the active module list. + */ + public void addModule(Module m) { + activeModules.add(m); + } + + /** + * Adds a module to the semester one module list. + * The module must not already exist in the semester one module list. + */ + public void addSemOneModule(Module m) { + semOneModules.add(m); + } + + /** + * Adds a module to the semester two module list. + * The module must not already exist in the semester two module list. + */ + public void addSemTwoModule(Module m) { + semTwoModules.add(m); + } + + /** + * Replaces the given module {@code target} in the list with {@code editedModule}. + * {@code target} must exist in the address book. + * The module identity of {@code editedModule} must not be the same as another existing module in the address book. + */ + public void setModule(Module target, Module editedModule) { + requireAllNonNull(target, editedModule); + + activeModules.setModule(target, editedModule); + } + + /** + * Assigns an {@code instructor} to the module with the given {@code moduleCode}. + * The module with the {@code moduleCode} must exist in the address book. + */ + public void assignInstructor(Person instructor, ModuleCode moduleCode) { + requireAllNonNull(instructor, moduleCode); + + activeModules.assignInstructor(instructor, moduleCode); + } + + public void unassignAllInstructors() { + activeModules.unassignAllInstructors(); + } + + /** + * Unassigns an {@code instructor} from the module with the given {@code moduleCode}. + * The module with the {@code moduleCode} must exist in the address book. + */ + public void unassignInstructor(Person instructor, ModuleCode moduleCode) { + requireAllNonNull(instructor, moduleCode); + + activeModules.unassignInstructor(instructor, moduleCode); + } + + /** + * Unassigns an {@code instructor} from all the modules that contains that instructor. + * The instructor must exist in at least one module. + * @throws CommandException if {@code instructor} does not exist in any of the modules. + */ + public void unassignInstructorFromAll(Person instructor) throws CommandException { + requireAllNonNull(instructor); + + activeModules.unassignInstructorFromAll(instructor); + } + + /** + * Checks whether an {@code instructor} in the module with the given {@code moduleCode} exists. + * The module with the {@code moduleCode} must exist in the address book. + * @return true if the {@code instructor} is an instructor of the module with the {@code moduleCode} + */ + public boolean moduleCodeHasInstructor(ModuleCode moduleCode, Person instructor) { + requireAllNonNull(instructor, moduleCode); + return activeModules.moduleCodeHasInstructor(moduleCode, instructor); + } + + /** + * Switches the active module list. + * */ + public void switchModuleList() { + activeModules = activeModules == semOneModules ? semTwoModules : semOneModules; + } + + /** + * Returns the active semester. + */ + public int getSemester() { + return activeModules == semOneModules ? 1 : 2; } //// util methods @@ -106,15 +304,66 @@ public ObservableList getPersonList() { return persons.asUnmodifiableObservableList(); } + @Override + public ObservableList getModuleList() { + return activeModules.asUnmodifiableObservableList(); + } + + @Override + public ObservableList getSemOneModuleList() { + return semOneModules.asUnmodifiableObservableList(); + } + + @Override + public ObservableList getSemTwoModuleList() { + return semTwoModules.asUnmodifiableObservableList(); + } + + private void reassignEditedInstructor(Person target, Person editedPerson) { + for (Module m: semOneModules) { + if (m.hasInstructor(target)) { + m.unassignInstructor(target); + m.assignInstructor(editedPerson); + semOneModules.setModule(m, m); + } + } + + for (Module m: semTwoModules) { + if (m.hasInstructor(target)) { + m.unassignInstructor(target); + m.assignInstructor(editedPerson); + semTwoModules.setModule(m, m); + } + } + } + + private void deleteInstructorAssignments(Person toBeRemoved) { + for (Module m : semOneModules) { + if (m.hasInstructor(toBeRemoved)) { + m.unassignInstructor(toBeRemoved); + semOneModules.setModule(m, m); + } + } + for (Module m : semTwoModules) { + if (m.hasInstructor(toBeRemoved)) { + m.unassignInstructor(toBeRemoved); + semTwoModules.setModule(m, m); + } + } + } + @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)); + && persons.equals(((AddressBook) other).persons) + && activeModules.equals(((AddressBook) other).activeModules) + && semOneModules.equals(((AddressBook) other).semOneModules) + && semTwoModules.equals(((AddressBook) other).semTwoModules)); } @Override public int hashCode() { - return persons.hashCode(); + return Objects.hash(persons.hashCode(), activeModules.hashCode()); } } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..d130f277f3a 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -5,6 +5,9 @@ import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCode; import seedu.address.model.person.Person; /** @@ -14,6 +17,9 @@ public interface Model { /** {@code Predicate} that always evaluate to true */ Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + /** {@code Predicate} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_MODULES = unused -> true; + /** * Replaces user prefs data with the data in {@code userPrefs}. */ @@ -58,10 +64,15 @@ public interface Model { boolean hasPerson(Person person); /** - * Deletes the given person. - * The person must exist in the address book. + * Returns true if contact list is empty. */ - void deletePerson(Person target); + boolean isEmptyPersonList(); + + /** + * Clear all modules from the contact list. + * The list must have at least one contact. + */ + void clearContacts(); /** * Adds the given person. @@ -69,6 +80,12 @@ public interface Model { */ void addPerson(Person person); + /** + * Deletes the given person. + * The person must exist in the address book. + */ + void deletePerson(Person target); + /** * Replaces the given person {@code target} with {@code editedPerson}. * {@code target} must exist in the address book. @@ -76,6 +93,12 @@ public interface Model { */ void setPerson(Person target, Person editedPerson); + /** + * Clear all modules from the module list. + * The list must have at least one module. + */ + void clearMod(); + /** Returns an unmodifiable view of the filtered person list */ ObservableList getFilteredPersonList(); @@ -84,4 +107,75 @@ public interface Model { * @throws NullPointerException if {@code predicate} is null. */ void updateFilteredPersonList(Predicate predicate); + + /** + * Returns true if a module with the same identity as {@code module} exists in the address book. + */ + boolean hasModule(Module module); + + /** + * Returns true if a module has a module code of {@code moduleCode} exists in the address book. + */ + boolean hasModuleCode(ModuleCode moduleCode); + + /** + * Returns true if module list is empty. + */ + boolean isEmptyModuleList(); + + /** + * Adds the given module. + * {@code module} must not already exist in the address book. + */ + void addModule(Module module); + + /** + * Removes the module with the given {@code moduleCode}. + * Module with the {@code moduleCode} must exist in the address book. + */ + void deleteModule(ModuleCode moduleCode); + + /** + * Assigns an {@code instructor} to the module with the given {@code moduleCode}. + * The module with the {@code moduleCode} must exist in the address book. + */ + void assignInstructor(Person instructor, ModuleCode moduleCode); + + void unassignAllInstructors(); + + /** + * Unassigns an {@code instructor} from the module with the given {@code moduleCode}. + * The module with the {@code moduleCode} must exist in the address book. + */ + void unassignInstructor(Person instructor, ModuleCode moduleCode); + + /** + * Unassigns an {@code instructor} from all modules. + */ + void unassignInstructorFromAll(Person instructor) throws CommandException; + + /** + * Returns true if the module with the given {@code moduleCode} has {@code instructor}. + */ + boolean moduleCodeHasInstructor(ModuleCode moduleCode, Person instructor); + + /** + * Switches the active module list. + */ + void switchModuleList(); + + /** + * Returns the active semester. + */ + int getSemester(); + + /** Returns an unmodifiable view of the filtered person list. */ + ObservableList getFilteredModuleList(); + + /** + * Updates the filter of the filtered module list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredModuleList(Predicate predicate); + } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 0650c954f5c..7d8148b6238 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -11,6 +11,9 @@ import javafx.collections.transformation.FilteredList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCode; import seedu.address.model.person.Person; /** @@ -22,6 +25,7 @@ public class ModelManager implements Model { private final AddressBook addressBook; private final UserPrefs userPrefs; private final FilteredList filteredPersons; + private FilteredList filteredModules; /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -35,6 +39,7 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs this.addressBook = new AddressBook(addressBook); this.userPrefs = new UserPrefs(userPrefs); filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + filteredModules = new FilteredList<>(this.addressBook.getModuleList()); } public ModelManager() { @@ -105,13 +110,98 @@ public void addPerson(Person person) { updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); } + @Override + public boolean isEmptyPersonList() { + return addressBook.getPersonList().isEmpty(); + } + + @Override + public void clearContacts() { + addressBook.clearContacts(); + } + @Override public void setPerson(Person target, Person editedPerson) { requireAllNonNull(target, editedPerson); - addressBook.setPerson(target, editedPerson); } + @Override + public void deleteModule(ModuleCode moduleCode) { + requireNonNull(moduleCode); + addressBook.removeModule(moduleCode); + } + + @Override + public boolean isEmptyModuleList() { + return addressBook.getModuleList().isEmpty(); + } + + @Override + public void clearMod() { + addressBook.clearMod(); + } + + @Override + public boolean hasModule(Module module) { + requireNonNull(module); + return addressBook.hasModule(module); + } + + @Override + public boolean hasModuleCode(ModuleCode moduleCode) { + requireNonNull(moduleCode); + return addressBook.hasModuleCode(moduleCode); + } + + @Override + public void addModule(Module module) { + addressBook.addModule(module); + updateFilteredModuleList(PREDICATE_SHOW_ALL_MODULES); + } + + @Override + public void assignInstructor(Person instructor, ModuleCode moduleCode) { + addressBook.assignInstructor(instructor, moduleCode); + updateFilteredModuleList(PREDICATE_SHOW_ALL_MODULES); + } + + @Override + public void unassignAllInstructors() { + addressBook.unassignAllInstructors(); + updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + updateFilteredModuleList(PREDICATE_SHOW_ALL_MODULES); + } + + @Override + public void unassignInstructor(Person instructor, ModuleCode moduleCode) { + addressBook.unassignInstructor(instructor, moduleCode); + updateFilteredModuleList(PREDICATE_SHOW_ALL_MODULES); + } + + @Override + public void unassignInstructorFromAll(Person instructor) throws CommandException { + addressBook.unassignInstructorFromAll(instructor); + updateFilteredModuleList(PREDICATE_SHOW_ALL_MODULES); + } + + @Override + public boolean moduleCodeHasInstructor(ModuleCode moduleCode, Person instructor) { + requireAllNonNull(instructor, moduleCode); + return addressBook.moduleCodeHasInstructor(moduleCode, instructor); + } + + @Override + public void switchModuleList() { + addressBook.switchModuleList(); + filteredModules = new FilteredList<>(this.addressBook.getModuleList()); + } + + @Override + public int getSemester() { + return addressBook.getSemester(); + } + //=========== Filtered Person List Accessors ============================================================= /** @@ -129,6 +219,23 @@ public void updateFilteredPersonList(Predicate predicate) { filteredPersons.setPredicate(predicate); } + //=========== Filtered Module List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Module} backed by the internal list of + * {@code versionedAddressBook} + */ + @Override + public ObservableList getFilteredModuleList() { + return filteredModules; + } + + @Override + public void updateFilteredModuleList(Predicate predicate) { + requireNonNull(predicate); + this.filteredModules.setPredicate(predicate); + } + @Override public boolean equals(Object obj) { // short circuit if same object @@ -145,7 +252,8 @@ public boolean equals(Object obj) { ModelManager other = (ModelManager) obj; return addressBook.equals(other.addressBook) && userPrefs.equals(other.userPrefs) - && filteredPersons.equals(other.filteredPersons); + && filteredPersons.equals(other.filteredPersons) + && filteredModules.equals(other.filteredModules); } } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 6ddc2cd9a29..cbfe1d8ab23 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -1,6 +1,7 @@ package seedu.address.model; import javafx.collections.ObservableList; +import seedu.address.model.module.Module; import seedu.address.model.person.Person; /** @@ -14,4 +15,23 @@ public interface ReadOnlyAddressBook { */ ObservableList getPersonList(); + /** + * Returns an unmodifiable view of the modules list. + * This list will not contain any duplicate modules. + */ + ObservableList getModuleList(); + + /** + * Returns an unmodifiable view of the semester one modules list. + * This list will not contain any duplicate modules. + */ + ObservableList getSemOneModuleList(); + + /** + * Returns an unmodifiable view of the semester two modules list. + * This list will not contain any duplicate modules. + */ + ObservableList getSemTwoModuleList(); + + } diff --git a/src/main/java/seedu/address/model/module/Module.java b/src/main/java/seedu/address/model/module/Module.java new file mode 100644 index 00000000000..b6fefca758c --- /dev/null +++ b/src/main/java/seedu/address/model/module/Module.java @@ -0,0 +1,134 @@ +package seedu.address.model.module; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import seedu.address.model.person.Person; + +/** + * Represents a module in FaculType. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Module { + + // Identity fields + private final ModuleCode moduleCode; + + // Data fields + private final ModuleName moduleName; + private final Set instructors = new HashSet<>(); + + /** + * Every field must be present and not null. + */ + public Module(ModuleCode moduleCode, ModuleName moduleName) { + requireAllNonNull(moduleCode, moduleName); + this.moduleCode = moduleCode; + this.moduleName = moduleName; + } + + /** + * Every field must be present and not null. + */ + public Module(ModuleCode moduleCode, ModuleName moduleName, Set instructors) { + requireAllNonNull(moduleCode, moduleName, instructors); + this.moduleCode = moduleCode; + this.moduleName = moduleName; + this.instructors.addAll(instructors); + } + + public ModuleCode getModuleCode() { + return moduleCode; + } + + public ModuleName getModuleName() { + return moduleName; + } + + public Set getInstructors() { + return Collections.unmodifiableSet(instructors); + } + + /** + * Empties out the instructor list of this module. + * @return this module with empty instructors list + */ + public Module moduleWithEmptyInstructors() { + Set emptyInstructors = new HashSet<>(); + Module newModule = new Module(this.moduleCode, this.moduleName, emptyInstructors); + return newModule; + } + + public void assignInstructor(Person instructor) { + this.instructors.add(instructor); + } + + /** + * Unassigns {@code instructor} from this module. + * @param instructor person in the filtered person list to be unassigned + */ + public void unassignInstructor(Person instructor) { + this.instructors.remove(instructor); + } + + public boolean hasInstructor(Person instructor) { + return this.instructors.contains(instructor); + } + + /** + * Returns true if both modules have the same code. + */ + public boolean isSameModule(Module otherModule) { + if (otherModule == this) { + return true; + } + + return otherModule != null + && otherModule.moduleCode.equals(moduleCode); + } + + /** + * Returns true if module has the specified module code. + */ + public boolean hasModuleCode(ModuleCode moduleCodeToCheck) { + return moduleCodeToCheck != null + && moduleCodeToCheck.equals(moduleCode); + } + + /** + * Returns true if both modules have the same identity and data fields. + * This defines a stronger notion of equality between two modules. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Module)) { + return false; + } + + Module m = (Module) other; + return m.moduleCode.equals(moduleCode) + && m.moduleName.equals(moduleName) + && m.instructors.equals(instructors); + + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(moduleCode); + } + + @Override + public String toString() { + return moduleCode + " " + moduleName; + } + +} diff --git a/src/main/java/seedu/address/model/module/ModuleCode.java b/src/main/java/seedu/address/model/module/ModuleCode.java new file mode 100644 index 00000000000..b4b3ebad925 --- /dev/null +++ b/src/main/java/seedu/address/model/module/ModuleCode.java @@ -0,0 +1,57 @@ +package seedu.address.model.module; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Module's code in FaculType. + * Guarantees: immutable; is valid as declared in {@link #isValidCode(String)} + */ +public class ModuleCode { + + public static final String MESSAGE_CONSTRAINTS = + "Codes should only contain alphanumeric characters, 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}+"; + + public final String moduleCode; + + /** + * Constructs a {@code Code}. + * + * @param moduleCode A valid code. + */ + public ModuleCode(String moduleCode) { + requireNonNull(moduleCode); + checkArgument(isValidCode(moduleCode), MESSAGE_CONSTRAINTS); + this.moduleCode = moduleCode.toUpperCase(); + } + + /** + * Returns true if a given string is a valid code. + */ + public static boolean isValidCode(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return moduleCode; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ModuleCode // instanceof handles nulls + && moduleCode.equals(((ModuleCode) other).moduleCode)); // state check + } + + @Override + public int hashCode() { + return moduleCode.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/module/ModuleName.java b/src/main/java/seedu/address/model/module/ModuleName.java new file mode 100644 index 00000000000..f9f9e1501b4 --- /dev/null +++ b/src/main/java/seedu/address/model/module/ModuleName.java @@ -0,0 +1,58 @@ +package seedu.address.model.module; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Module's name in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} + */ +public class ModuleName { + + public static final String MESSAGE_CONSTRAINTS = + "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 moduleName; + + /** + * Constructs a {@code Name}. + * + * @param name A valid name. + */ + public ModuleName(String name) { + requireNonNull(name); + checkArgument(isValidName(name), MESSAGE_CONSTRAINTS); + moduleName = 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 moduleName; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ModuleName // instanceof handles nulls + && moduleName.equals(((ModuleName) other).moduleName)); // state check + } + + @Override + public int hashCode() { + return moduleName.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/module/UniqueModuleList.java b/src/main/java/seedu/address/model/module/UniqueModuleList.java new file mode 100644 index 00000000000..84d65d3e44e --- /dev/null +++ b/src/main/java/seedu/address/model/module/UniqueModuleList.java @@ -0,0 +1,279 @@ +package seedu.address.model.module; + +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.commons.core.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.module.exceptions.DuplicateModuleException; +import seedu.address.model.module.exceptions.ModuleNotFoundException; +import seedu.address.model.person.Person; + +/** + * A list of modules that enforces uniqueness between its elements and does not allow nulls. + * A module is considered unique by comparing using {@code Module#isSameModule(Module)}. As such, adding and updating of + * modlues uses Module#isSameModule(Module) for equality so as to ensure that the module being added or updated is + * unique in terms of identity in the ModuleList. However, the removal of a module uses Module#equals(Object) so + * as to ensure that the module with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Module#isSameModule(Module) + */ +public class UniqueModuleList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent module as the given argument. + */ + public boolean contains(Module moduleToCheck) { + requireNonNull(moduleToCheck); + return internalList.stream().anyMatch(moduleToCheck::isSameModule); + } + + /** + * Returns true if the list contains a module with the given module code. + */ + public boolean containsModuleCode(ModuleCode moduleCodeToCheck) { + requireNonNull(moduleCodeToCheck); + return internalList.stream().anyMatch(module -> module.hasModuleCode(moduleCodeToCheck)); + } + + /** + * Adds a module to the list. + * The module must not already exist in the list. + */ + public void add(Module moduleToAdd) { + requireNonNull(moduleToAdd); + if (contains(moduleToAdd)) { + throw new DuplicateModuleException(); + } + internalList.add(moduleToAdd); + } + + /** + * Replaces the module {@code target} in the list with {@code editedModule}. + * {@code target} must exist in the list. + * The module identity of {@code editedModule} must not be the same as another existing module in the list. + */ + public void setModule(Module target, Module editedModule) { + requireAllNonNull(target, editedModule); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new ModuleNotFoundException(); + } + + if (!target.isSameModule(editedModule) && contains(editedModule)) { + throw new DuplicateModuleException(); + } + + internalList.set(index, editedModule); + } + + /** + * Check whether {@code instructor} exists in at least one module. + */ + public boolean isAnInstructor(Person instructor) { + requireNonNull(instructor); + boolean instructorExistInAnyModule = false; + for (Module toCheck : internalList) { + if (toCheck.hasInstructor(instructor)) { + instructorExistInAnyModule = true; + } + } + + return instructorExistInAnyModule; + } + + /** + * Assigns an instructor to the module with the equivalent module code from the list. + * The module with the module code must exist in the list. + */ + public void assignInstructor(Person instructor, ModuleCode moduleCodeToAssign) { + requireAllNonNull(instructor, moduleCodeToAssign); + int indexOfModuleToAssign = 0; + while (!internalList.get(indexOfModuleToAssign).hasModuleCode(moduleCodeToAssign) + && indexOfModuleToAssign < internalList.size()) { + indexOfModuleToAssign++; + } + Module moduleToAssign = internalList.get(indexOfModuleToAssign); + moduleToAssign.assignInstructor(instructor); + internalList.set((indexOfModuleToAssign), moduleToAssign); + } + + /** + * Unassigns all instructors from all modules. + */ + public void unassignAllInstructors() { + for (int index = 0; index < internalList.size(); index++) { + Module toSet = internalList.get(index); + Module moduleWithEmptyInstructors = toSet.moduleWithEmptyInstructors(); + internalList.set((index), moduleWithEmptyInstructors); + } + } + + /** + * Unassigns an instructor from the module with the equivalent module code from the list. + * The module with the module code must exist in the list. + */ + public void unassignInstructor(Person instructor, ModuleCode moduleToUnassign) { + requireAllNonNull(instructor, moduleToUnassign); + + int indexOfModuleToUnassign = 0; + while (!internalList.get(indexOfModuleToUnassign).hasModuleCode(moduleToUnassign) + && indexOfModuleToUnassign < internalList.size()) { + indexOfModuleToUnassign++; + } + Module toSet = internalList.get(indexOfModuleToUnassign); + toSet.unassignInstructor(instructor); + internalList.set((indexOfModuleToUnassign), toSet); + } + + /** + * Unassigns {@code instructor} from all modules that contains it. + */ + public void unassignInstructorFromAll(Person instructor) throws CommandException { + + if (isAnInstructor(instructor)) { + for (int index = 0; index < internalList.size(); index++) { + + Module toSet = internalList.get(index); + + if (toSet.hasInstructor(instructor)) { + toSet.unassignInstructor(instructor); + internalList.set((index), toSet); + } + } + + } else { + throw new CommandException(String.format(Messages.MESSAGE_PERSON_IS_NOT_AN_INSTRUCTOR, + instructor.getName())); + } + } + + /** + * Checks whether an {@code instructor} in the module with the given {@code moduleCode} exists. + * The module with the {@code moduleCode} must exist in the address book. + * @return true if the {@code instructor} is an instructor of the module with the {@code moduleCode} + */ + public boolean moduleCodeHasInstructor(ModuleCode moduleCode, Person instructor) { + requireAllNonNull(instructor, moduleCode); + + for (Module moduleToCheck : internalList) { + if (moduleToCheck.hasModuleCode(moduleCode) + && !moduleToCheck.hasInstructor(instructor)) { + return false; + } + } + return true; + } + + /** + * Clear all the modules inside the list. + */ + public void clearAll() { + internalList.clear(); + } + + /** + * Returns true if the internal list is empty. + */ + public boolean isEmptyList() { + return internalList.isEmpty(); + } + + /** + * Removes the equivalent module from the list. + * The module must exist in the list. + */ + public void remove(Module toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new ModuleNotFoundException(); + } + } + + /** + * Removes the module with the equivalent module code from the list. + * The module with the module code must exist in the list. + */ + public void removeModuleWithCode(ModuleCode moduleToRemove) { + requireNonNull(moduleToRemove); + int indexOfModuleToRemove = 0; + while (!internalList.get(indexOfModuleToRemove).hasModuleCode(moduleToRemove) + && indexOfModuleToRemove < internalList.size()) { + indexOfModuleToRemove++; + } + internalList.remove(indexOfModuleToRemove); + } + + public void setModules(UniqueModuleList replacement) { + requireNonNull(replacement); + setModules(replacement.getInternalList()); + } + + + /** + * Replaces the contents of this list with {@code modules}. + * {@code modules} must not contain duplicate modules. + */ + public void setModules(List modules) { + requireAllNonNull(modules); + if (!modulesAreUnique(modules)) { + throw new DuplicateModuleException(); + } + + internalList.setAll(modules); + } + + /** + * 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 UniqueModuleList // instanceof handles nulls + && internalList.equals(((UniqueModuleList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns true if {@code modules} contains only unique modules. + */ + private boolean modulesAreUnique(List modules) { + for (int i = 0; i < modules.size() - 1; i++) { + for (int j = i + 1; j < modules.size(); j++) { + if (modules.get(i).isSameModule(modules.get(j))) { + return false; + } + } + } + return true; + } + + public ObservableList getInternalList() { + return internalList; + } +} diff --git a/src/main/java/seedu/address/model/module/exceptions/DuplicateModuleException.java b/src/main/java/seedu/address/model/module/exceptions/DuplicateModuleException.java new file mode 100644 index 00000000000..95677a16ff1 --- /dev/null +++ b/src/main/java/seedu/address/model/module/exceptions/DuplicateModuleException.java @@ -0,0 +1,11 @@ +package seedu.address.model.module.exceptions; + +/** + * Signals that the operation will result in duplicate Modules (Modules are considered duplicates if they have the same + * code). + */ +public class DuplicateModuleException extends RuntimeException { + public DuplicateModuleException() { + super("Operation would result in duplicate modules"); + } +} diff --git a/src/main/java/seedu/address/model/module/exceptions/ModuleNotFoundException.java b/src/main/java/seedu/address/model/module/exceptions/ModuleNotFoundException.java new file mode 100644 index 00000000000..52abdad64ea --- /dev/null +++ b/src/main/java/seedu/address/model/module/exceptions/ModuleNotFoundException.java @@ -0,0 +1,6 @@ +package seedu.address.model.module.exceptions; + +/** + * Signals that the operation is unable to find the specified module. + */ +public class ModuleNotFoundException extends RuntimeException {} diff --git a/src/main/java/seedu/address/model/module/predicates/ModuleCodeContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/module/predicates/ModuleCodeContainsKeywordsPredicate.java new file mode 100644 index 00000000000..efbebc79930 --- /dev/null +++ b/src/main/java/seedu/address/model/module/predicates/ModuleCodeContainsKeywordsPredicate.java @@ -0,0 +1,28 @@ +package seedu.address.model.module.predicates; + +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.module.Module; + +public class ModuleCodeContainsKeywordsPredicate implements Predicate { + + private final String keyword; + + public ModuleCodeContainsKeywordsPredicate(String keyword) { + this.keyword = keyword; + } + + @Override + public boolean test(Module module) { + return StringUtil.containsSubWordOrWordIgnoreCase(module.getModuleCode().toString(), keyword); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ModuleCodeContainsKeywordsPredicate // instanceof handles nulls + && keyword.equals(((ModuleCodeContainsKeywordsPredicate) other).keyword)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/module/predicates/ModuleInstructorsContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/module/predicates/ModuleInstructorsContainsKeywordsPredicate.java new file mode 100644 index 00000000000..f589c6f2df8 --- /dev/null +++ b/src/main/java/seedu/address/model/module/predicates/ModuleInstructorsContainsKeywordsPredicate.java @@ -0,0 +1,41 @@ +package seedu.address.model.module.predicates; + +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.module.Module; +import seedu.address.model.person.Person; + +public class ModuleInstructorsContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public ModuleInstructorsContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Module module) { + Set instructors = module.getInstructors(); + return keywords.stream() + .allMatch(keyword -> anyInstructorsNameContainsKeyword(instructors, keyword)); + } + + private static boolean anyInstructorsNameContainsKeyword(Set instructors, String keyword) { + for (Person instructor : instructors) { + if (StringUtil.containsSubWordOrWordIgnoreCase(instructor.getName().toString(), keyword)) { + return true; + } + continue; + } + return false; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ModuleInstructorsContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((ModuleInstructorsContainsKeywordsPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/module/predicates/ModuleNameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/module/predicates/ModuleNameContainsKeywordsPredicate.java new file mode 100644 index 00000000000..c1d5f9ee022 --- /dev/null +++ b/src/main/java/seedu/address/model/module/predicates/ModuleNameContainsKeywordsPredicate.java @@ -0,0 +1,29 @@ +package seedu.address.model.module.predicates; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.module.Module; + +public class ModuleNameContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public ModuleNameContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Module module) { + return keywords.stream() + .allMatch(keyword -> StringUtil.containsSubWordOrWordIgnoreCase( + module.getModuleName().moduleName, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ModuleNameContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((ModuleNameContainsKeywordsPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/person/Department.java b/src/main/java/seedu/address/model/person/Department.java new file mode 100644 index 00000000000..7946d5c6eca --- /dev/null +++ b/src/main/java/seedu/address/model/person/Department.java @@ -0,0 +1,57 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Person's department in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidDepartment(String)} + */ +public class Department { + + public static final String MESSAGE_CONSTRAINTS = "Departments can take any values, and it should not be blank"; + + /* + * The first character of the department must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String VALIDATION_REGEX = "[^\\s].*"; + + public final String value; + + /** + * Constructs an {@code Department}. + * + * @param department A valid department. + */ + public Department(String department) { + requireNonNull(department); + checkArgument(isValidDepartment(department), MESSAGE_CONSTRAINTS); + value = department; + } + + /** + * Returns true if a given string is a valid email. + */ + public static boolean isValidDepartment(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Department // instanceof handles nulls + && value.equals(((Department) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Office.java similarity index 53% rename from src/main/java/seedu/address/model/person/Address.java rename to src/main/java/seedu/address/model/person/Office.java index 60472ca22a0..7ce511d383e 100644 --- a/src/main/java/seedu/address/model/person/Address.java +++ b/src/main/java/seedu/address/model/person/Office.java @@ -4,15 +4,15 @@ import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Person's address in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} + * Represents a Person's office in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidOffice(String)} */ -public class Address { +public class Office { - public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, and it should not be blank"; + public static final String MESSAGE_CONSTRAINTS = "Offices can take any values, and it should not be blank"; /* - * The first character of the address must not be a whitespace, + * The first character of the office must not be a whitespace, * otherwise " " (a blank string) becomes a valid input. */ public static final String VALIDATION_REGEX = "[^\\s].*"; @@ -20,20 +20,20 @@ public class Address { public final String value; /** - * Constructs an {@code Address}. + * Constructs an {@code Office}. * - * @param address A valid address. + * @param office A valid department. */ - public Address(String address) { - requireNonNull(address); - checkArgument(isValidAddress(address), MESSAGE_CONSTRAINTS); - value = address; + public Office(String office) { + requireNonNull(office); + checkArgument(isValidOffice(office), MESSAGE_CONSTRAINTS); + value = office; } /** * Returns true if a given string is a valid email. */ - public static boolean isValidAddress(String test) { + public static boolean isValidOffice(String test) { return test.matches(VALIDATION_REGEX); } @@ -45,13 +45,12 @@ public String toString() { @Override public boolean equals(Object other) { return other == this // short circuit if same object - || (other instanceof Address // instanceof handles nulls - && value.equals(((Address) other).value)); // state check + || (other instanceof Office // instanceof handles nulls + && value.equals(((Office) other).value)); // state check } @Override public int hashCode() { return value.hashCode(); } - } diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index 557a7a60cd5..f5a5f797c3c 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -21,18 +21,23 @@ public class Person { private final Email email; // Data fields - private final Address address; + private final Department department; + private final Office office; + private final Remark remark; 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 Person(Name name, Phone phone, Email email, Department department, Office office, + Remark remark, Set tags) { + requireAllNonNull(name, phone, email, department, office, tags); this.name = name; this.phone = phone; this.email = email; - this.address = address; + this.department = department; + this.office = office; + this.remark = remark; this.tags.addAll(tags); } @@ -48,8 +53,16 @@ public Email getEmail() { return email; } - public Address getAddress() { - return address; + public Department getDepartment() { + return department; + } + + public Office getOffice() { + return office; + } + + public Remark getRemark() { + return remark; } /** @@ -92,14 +105,15 @@ public boolean equals(Object other) { return otherPerson.getName().equals(getName()) && otherPerson.getPhone().equals(getPhone()) && otherPerson.getEmail().equals(getEmail()) - && otherPerson.getAddress().equals(getAddress()) + && otherPerson.getDepartment().equals(getDepartment()) + && otherPerson.getOffice().equals(getOffice()) && otherPerson.getTags().equals(getTags()); } @Override public int hashCode() { // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); + return Objects.hash(name, phone, email, department, office, remark, tags); } @Override @@ -110,8 +124,12 @@ public String toString() { .append(getPhone()) .append(" Email: ") .append(getEmail()) - .append(" Address: ") - .append(getAddress()) + .append(" Department: ") + .append(getDepartment()) + .append(" Office: ") + .append(getOffice()) + .append(" Remark: ") + .append(getRemark()) .append(" Tags: "); getTags().forEach(builder::append); return builder.toString(); diff --git a/src/main/java/seedu/address/model/person/Remark.java b/src/main/java/seedu/address/model/person/Remark.java new file mode 100644 index 00000000000..d906cc5ba08 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Remark.java @@ -0,0 +1,38 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; + +/** + * Represents a Person's remark in the address book. + * Guarantees: immutable; is always valid + */ +public class Remark { + public final String value; + + /** + * Constructs an {@code Remark}. + * + * @param remark A remark. + */ + public Remark(String remark) { + requireNonNull(remark); + value = remark; + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Remark // instanceof handles nulls + && value.equals(((Remark) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java index 0fee4fe57e6..5fb77a5d905 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/address/model/person/UniquePersonList.java @@ -79,6 +79,21 @@ public void remove(Person toRemove) { } } + /** + * Clear all the contacts inside the list. + */ + public void clearAll() { + internalList.clear(); + } + + /** + * Returns true if the internal list is empty. + * @return + */ + public boolean isEmptyList() { + return internalList.isEmpty(); + } + public void setPersons(UniquePersonList replacement) { requireNonNull(replacement); internalList.setAll(replacement.internalList); diff --git a/src/main/java/seedu/address/model/person/predicates/DepartmentContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/DepartmentContainsKeywordsPredicate.java new file mode 100644 index 00000000000..f500041765f --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicates/DepartmentContainsKeywordsPredicate.java @@ -0,0 +1,37 @@ +package seedu.address.model.person.predicates; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Department} matches any of the keywords given. + */ +public class DepartmentContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public DepartmentContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + if (keywords.isEmpty()) { + return false; + } + + return keywords.stream() + .allMatch(keyword -> StringUtil.containsSubWordOrWordIgnoreCase(person.getDepartment().value, + keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DepartmentContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((DepartmentContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/predicates/EmailContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/EmailContainsKeywordsPredicate.java new file mode 100644 index 00000000000..43d502b4ea7 --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicates/EmailContainsKeywordsPredicate.java @@ -0,0 +1,38 @@ +package seedu.address.model.person.predicates; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +public class EmailContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public EmailContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + if (keywords.isEmpty()) { + return false; + } + + return keywords.stream() + .allMatch(keyword -> StringUtil.containsSubWordOrWordIgnoreCase(person.getEmail().value, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EmailContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((EmailContainsKeywordsPredicate) other).keywords)); // state check + } + + @Override + public String toString() { + return Arrays.toString(keywords.toArray()); + } +} diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/NameContainsKeywordsPredicate.java similarity index 67% rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java rename to src/main/java/seedu/address/model/person/predicates/NameContainsKeywordsPredicate.java index c9b5868427c..b98df141c4f 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/person/predicates/NameContainsKeywordsPredicate.java @@ -1,9 +1,11 @@ -package seedu.address.model.person; +package seedu.address.model.person.predicates; +import java.util.Arrays; import java.util.List; import java.util.function.Predicate; import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; /** * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. @@ -17,8 +19,12 @@ public NameContainsKeywordsPredicate(List keywords) { @Override public boolean test(Person person) { + if (keywords.isEmpty()) { + return false; + } + return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + .allMatch(keyword -> StringUtil.containsSubWordOrWordIgnoreCase(person.getName().fullName, keyword)); } @Override @@ -28,4 +34,9 @@ public boolean equals(Object other) { && keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check } + @Override + public String toString() { + return Arrays.toString(keywords.toArray()); + } + } diff --git a/src/main/java/seedu/address/model/person/predicates/OfficeContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/OfficeContainsKeywordsPredicate.java new file mode 100644 index 00000000000..29eeee0cc4f --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicates/OfficeContainsKeywordsPredicate.java @@ -0,0 +1,36 @@ +package seedu.address.model.person.predicates; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Office} matches any of the keywords given. + */ +public class OfficeContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public OfficeContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + if (keywords.isEmpty()) { + return false; + } + + return keywords.stream() + .allMatch(keyword -> StringUtil.containsSubWordOrWordIgnoreCase(person.getOffice().value, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof OfficeContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((OfficeContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/predicates/PhoneContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/PhoneContainsKeywordsPredicate.java new file mode 100644 index 00000000000..e5777de95f8 --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicates/PhoneContainsKeywordsPredicate.java @@ -0,0 +1,37 @@ +package seedu.address.model.person.predicates; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Phone} matches all the keywords given. + */ +public class PhoneContainsKeywordsPredicate implements Predicate { + + private final List keywords; + + public PhoneContainsKeywordsPredicate(List keyword) { + this.keywords = keyword; + } + + @Override + public boolean test(Person person) { + if (keywords.isEmpty()) { + return false; + } + + return keywords.stream() + .allMatch(keyword -> StringUtil.containsSubWordOrWordIgnoreCase(person.getPhone().value, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PhoneContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((PhoneContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/predicates/RemarkContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/RemarkContainsKeywordsPredicate.java new file mode 100644 index 00000000000..a6a2c6e8496 --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicates/RemarkContainsKeywordsPredicate.java @@ -0,0 +1,36 @@ +package seedu.address.model.person.predicates; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Remark} matches any of the keywords given. + */ +public class RemarkContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public RemarkContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + if (keywords.isEmpty()) { + return false; + } + + return keywords.stream() + .allMatch(keyword -> StringUtil.containsSubWordOrWordIgnoreCase(person.getRemark().value, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof RemarkContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((RemarkContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/person/predicates/TagContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/predicates/TagContainsKeywordsPredicate.java new file mode 100644 index 00000000000..d52d0f9995d --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicates/TagContainsKeywordsPredicate.java @@ -0,0 +1,45 @@ +package seedu.address.model.person.predicates; + +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; +import seedu.address.model.tag.Tag; + +/** + * Tests that a {@code Person}'s {@code Tag} matches any of the keywords given. + */ +public class TagContainsKeywordsPredicate implements Predicate { + private final List tagKeywords; + + public TagContainsKeywordsPredicate(List tagKeywords) { + this.tagKeywords = tagKeywords; + } + + @Override + public boolean test(Person person) { + if (tagKeywords.isEmpty()) { + return false; + } + + return tagKeywords.stream() + .allMatch(keyword -> anyTagsContainsKeyword(person.getTags(), keyword)); + } + + /** + * Returns true if the {@code keyword} is contained in any of the tags. + */ + private static boolean anyTagsContainsKeyword(Set tagSet, String keyword) { + return tagSet.stream().anyMatch(tag -> StringUtil.containsSubWordOrWordIgnoreCase(tag.tagName, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TagContainsKeywordsPredicate // instanceof handles nulls + && tagKeywords.equals(((TagContainsKeywordsPredicate) other).tagKeywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..27a5a027929 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -6,37 +6,75 @@ import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; +import seedu.address.model.module.Module; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.module.ModuleName; +import seedu.address.model.person.Department; import seedu.address.model.person.Email; import seedu.address.model.person.Name; +import seedu.address.model.person.Office; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Remark; import seedu.address.model.tag.Tag; /** * Contains utility methods for populating {@code AddressBook} with sample data. */ public class SampleDataUtil { + + public static final Remark EMPTY_REMARK = new Remark(""); + 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"), - getTagSet("friends")), + new Department("Computer Science"), new Office("COM1-02-01"), EMPTY_REMARK, + getTagSet("lecturer")), new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), + new Department("Business Analytics"), new Office("COM1-02-10"), EMPTY_REMARK, + getTagSet("lecturer", "friend")), 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"), - getTagSet("neighbours")), + new Department("Data Science and Analytics"), new Office("COM1-01-05"), EMPTY_REMARK, + getTagSet("senior")), new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), + new Department("Information Security"), new Office("COM1-01-06"), EMPTY_REMARK, + getTagSet("friend")), new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), + new Department("Information Systems"), new Office("COM1-02-04"), EMPTY_REMARK, + getTagSet("senior", "friend")), new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) + new Department("Computer Engineering"), new Office("COM1-02-05"), EMPTY_REMARK, + getTagSet("associate")) + }; + } + + public static Module[] getSampleSemOneModules() { + return new Module[] { + new Module(new ModuleCode("CS1010"), new ModuleName("Programming Methodology")), + new Module(new ModuleCode("CS1231"), new ModuleName("Discrete Structures")), + new Module(new ModuleCode("IS1103"), new ModuleName("Ethics in Computing")), + new Module(new ModuleCode("MA1101R"), new ModuleName("Linear Algebra")), + new Module(new ModuleCode("MA1521"), new ModuleName("Calculus for Computing")), + new Module(new ModuleCode("CS2040"), new ModuleName("Data Structures and Algorithms")), + new Module(new ModuleCode("CS2103"), new ModuleName("Software Engineering")), + new Module(new ModuleCode("CS2105"), new ModuleName("Introduction to Computer Networks")), + new Module(new ModuleCode("CS3203"), new ModuleName("Software Engineering Project")), + new Module(new ModuleCode("IS2102"), new ModuleName( + "Enterprise Systems Architecture and Design")), + }; + } + + public static Module[] getSampleSemTwoModules() { + return new Module[] { + new Module(new ModuleCode("CS1101S"), new ModuleName("Programming Methodology")), + new Module(new ModuleCode("CS1231"), new ModuleName("Discrete Structures")), + new Module(new ModuleCode("CS2030"), new ModuleName("Programming Methodology II")), + new Module(new ModuleCode("CS2102"), new ModuleName("Database Systems")), + new Module(new ModuleCode("CS2106"), new ModuleName("Introduction to Operating Systems")), + new Module(new ModuleCode("IS2101"), new ModuleName("Business and Technical Communication")), + new Module(new ModuleCode("ST2334"), new ModuleName("Probability and Statistics")), + new Module(new ModuleCode("CS3210"), new ModuleName("Parallel Computing")), + new Module(new ModuleCode("CS3230"), new ModuleName("Design and Analysis of Algorithms")), }; } @@ -45,6 +83,12 @@ public static ReadOnlyAddressBook getSampleAddressBook() { for (Person samplePerson : getSamplePersons()) { sampleAb.addPerson(samplePerson); } + for (Module sampleModule : getSampleSemOneModules()) { + sampleAb.addSemOneModule(sampleModule); + } + for (Module sampleModule : getSampleSemTwoModules()) { + sampleAb.addSemTwoModule(sampleModule); + } return sampleAb; } @@ -57,4 +101,11 @@ public static Set getTagSet(String... strings) { .collect(Collectors.toSet()); } + /** + * Returns a Person set containing the list of persons given. + */ + public static Set getPersonSet(Person... persons) { + return Arrays.stream(persons).collect(Collectors.toSet()); + } + } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedModule.java b/src/main/java/seedu/address/storage/JsonAdaptedModule.java new file mode 100644 index 00000000000..bd5ee9a237a --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedModule.java @@ -0,0 +1,82 @@ +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.module.Module; +import seedu.address.model.module.ModuleCode; +import seedu.address.model.module.ModuleName; +import seedu.address.model.person.Person; + +public class JsonAdaptedModule { + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Module's %s field is missing!"; + + private final String moduleCode; + private final String moduleName; + private final List instructors = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedModule} with the given module details. + */ + @JsonCreator + public JsonAdaptedModule(@JsonProperty("moduleCode") String moduleCode, + @JsonProperty("moduleName") String moduleName, + @JsonProperty("instructors") List instructors) { + this.moduleCode = moduleCode; + this.moduleName = moduleName; + if (instructors != null) { + this.instructors.addAll(instructors); + } + } + + /** + * Converts a given {@code Module} into this class for Jackson use. + */ + public JsonAdaptedModule(Module source) { + moduleCode = source.getModuleCode().moduleCode; + moduleName = source.getModuleName().moduleName; + instructors.addAll(source.getInstructors().stream() + .map(JsonAdaptedPerson::new) + .collect(Collectors.toList())); + } + + /** + * Converts this Jackson-friendly adapted module object into the model's {@code Module} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted module. + */ + public Module toModelType() throws IllegalValueException { + final List instructors = new ArrayList<>(); + for (JsonAdaptedPerson person : this.instructors) { + instructors.add(person.toModelType()); + } + + if (moduleCode == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + ModuleCode.class.getSimpleName())); + } + if (!ModuleCode.isValidCode(moduleCode)) { + throw new IllegalValueException(ModuleCode.MESSAGE_CONSTRAINTS); + } + final ModuleCode modelCode = new ModuleCode(moduleCode); + + if (moduleName == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + ModuleName.class.getSimpleName())); + } + if (!ModuleName.isValidName(moduleName)) { + throw new IllegalValueException(ModuleName.MESSAGE_CONSTRAINTS); + } + final ModuleName modelName = new ModuleName(moduleName); + + final Set modelPersons = new HashSet<>(instructors); + return new Module(modelCode, modelName, modelPersons); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java index a6321cec2ea..ea5fe17760a 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java @@ -10,11 +10,13 @@ import com.fasterxml.jackson.annotation.JsonProperty; import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; +import seedu.address.model.person.Department; import seedu.address.model.person.Email; import seedu.address.model.person.Name; +import seedu.address.model.person.Office; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; +import seedu.address.model.person.Remark; import seedu.address.model.tag.Tag; /** @@ -27,7 +29,9 @@ class JsonAdaptedPerson { private final String name; private final String phone; private final String email; - private final String address; + private final String department; + private final String office; + private final String remark; private final List tagged = new ArrayList<>(); /** @@ -35,12 +39,15 @@ class JsonAdaptedPerson { */ @JsonCreator public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, - @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tagged") List tagged) { + @JsonProperty("email") String email, @JsonProperty("department") String department, + @JsonProperty("office") String office, @JsonProperty("remark") String remark, + @JsonProperty("tagged") List tagged) { this.name = name; this.phone = phone; this.email = email; - this.address = address; + this.department = department; + this.office = office; + this.remark = remark; if (tagged != null) { this.tagged.addAll(tagged); } @@ -53,7 +60,9 @@ public JsonAdaptedPerson(Person source) { name = source.getName().fullName; phone = source.getPhone().value; email = source.getEmail().value; - address = source.getAddress().value; + department = source.getDepartment().value; + office = source.getOffice().value; + remark = source.getRemark().value; tagged.addAll(source.getTags().stream() .map(JsonAdaptedTag::new) .collect(Collectors.toList())); @@ -94,16 +103,33 @@ 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 (department == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, Department.class.getSimpleName())); } - if (!Address.isValidAddress(address)) { - throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); + + if (!Department.isValidDepartment(department)) { + throw new IllegalValueException(Department.MESSAGE_CONSTRAINTS); + } + final Department modelDepartment = new Department(department); + + if (office == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, Office.class.getSimpleName())); + } + + if (!Office.isValidOffice(office)) { + throw new IllegalValueException(Office.MESSAGE_CONSTRAINTS); + } + final Office modelOffice = new Office(office); + + if (remark == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Remark.class.getSimpleName())); } - final Address modelAddress = new Address(address); + final Remark modelRemark = new Remark(remark); final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); + return new Person(modelName, modelPhone, modelEmail, modelDepartment, modelOffice, modelRemark, modelTags); } } diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java index 5efd834091d..6962b1f803c 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java @@ -11,6 +11,7 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.module.Module; import seedu.address.model.person.Person; /** @@ -20,15 +21,28 @@ class JsonSerializableAddressBook { public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; + public static final String MESSAGE_DUPLICATE_MODULE = "Modules list contains duplicate module(s)."; private final List persons = new ArrayList<>(); + private final List semOneModules = new ArrayList<>(); + private final List semTwoModules = new ArrayList<>(); /** - * Constructs a {@code JsonSerializableAddressBook} with the given persons. + * Constructs a {@code JsonSerializableAddressBook} with the given persons and modules. */ @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { - this.persons.addAll(persons); + public JsonSerializableAddressBook(@JsonProperty("persons") List persons, + @JsonProperty("semOneModules") List semOneModules, + @JsonProperty("semTwoModules") List semTwoModules) { + if (persons != null) { + this.persons.addAll(persons); + } + if (semOneModules != null) { + this.semOneModules.addAll(semOneModules); + } + if (semTwoModules != null) { + this.semTwoModules.addAll(semTwoModules); + } } /** @@ -38,6 +52,10 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; + public static final String USERGUIDE_URL = "https://ay2021s1-cs2103-t14-1.github.io/tp/UserGuide.html"; 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..8e3b820939d 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -4,7 +4,9 @@ import javafx.event.ActionEvent; import javafx.fxml.FXML; +import javafx.scene.Node; import javafx.scene.control.MenuItem; +import javafx.scene.control.TabPane; import javafx.scene.control.TextInputControl; import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; @@ -32,6 +34,7 @@ public class MainWindow extends UiPart { // Independent Ui parts residing in this Ui container private PersonListPanel personListPanel; + private ModuleListPanel moduleListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; @@ -41,9 +44,15 @@ public class MainWindow extends UiPart { @FXML private MenuItem helpMenuItem; + @FXML + private TabPane semsPanelPlaceholder; + @FXML private StackPane personListPanelPlaceholder; + @FXML + private StackPane moduleListPanelPlaceholder; + @FXML private StackPane resultDisplayPlaceholder; @@ -113,6 +122,9 @@ void fillInnerParts() { personListPanel = new PersonListPanel(logic.getFilteredPersonList()); personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + moduleListPanel = new ModuleListPanel(logic.getFilteredModuleList()); + moduleListPanelPlaceholder.getChildren().add(moduleListPanel.getRoot()); + resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); @@ -133,6 +145,8 @@ private void setWindowDefaultSize(GuiSettings guiSettings) { primaryStage.setX(guiSettings.getWindowCoordinates().getX()); primaryStage.setY(guiSettings.getWindowCoordinates().getY()); } + + primaryStage.setMinWidth(GuiSettings.getMinimumWidth()); } /** @@ -151,6 +165,26 @@ void show() { primaryStage.show(); } + /** + * Switches the semester view. + */ + @FXML + private void handleSwitchSem() { + int semester = logic.getSemester(); + int toIndex = semester == 1 ? 0 : 1; + + Node content = semsPanelPlaceholder.getSelectionModel().getSelectedItem().getContent(); + semsPanelPlaceholder.getSelectionModel().getSelectedItem().setContent(null); + semsPanelPlaceholder.getSelectionModel().getSelectedItem().setDisable(true); + + moduleListPanel = new ModuleListPanel(logic.getFilteredModuleList()); + moduleListPanelPlaceholder.getChildren().add(moduleListPanel.getRoot()); + + semsPanelPlaceholder.getSelectionModel().select(toIndex); + semsPanelPlaceholder.getSelectionModel().getSelectedItem().setDisable(false); + semsPanelPlaceholder.getSelectionModel().getSelectedItem().setContent(content); + } + /** * Closes the application. */ @@ -167,6 +201,10 @@ public PersonListPanel getPersonListPanel() { return personListPanel; } + public ModuleListPanel getModuleListPanel() { + return moduleListPanel; + } + /** * Executes the command and returns the result. * @@ -182,6 +220,10 @@ private CommandResult executeCommand(String commandText) throws CommandException handleHelp(); } + if (commandResult.isSwitchSem()) { + handleSwitchSem(); + } + if (commandResult.isExit()) { handleExit(); } diff --git a/src/main/java/seedu/address/ui/ModuleCard.java b/src/main/java/seedu/address/ui/ModuleCard.java new file mode 100644 index 00000000000..29e4dd6c1e6 --- /dev/null +++ b/src/main/java/seedu/address/ui/ModuleCard.java @@ -0,0 +1,85 @@ +package seedu.address.ui; + +import java.util.Comparator; + +import javafx.application.Platform; +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.module.Module; + + +/** + * A UI component that displays information of a {@code Module}. + */ +public class ModuleCard extends UiPart { + private static final String FXML = "ModuleListCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + public final Module module; + + @javafx.fxml.FXML + private HBox cardPane; + @FXML + private Label moduleCode; + @FXML + private Label moduleName; + @FXML + private FlowPane instructors; + + /** + * Creates a {@code ModuleCard} with the given {@code Module} and index to display. + */ + public ModuleCard(Module module, int displayedIndex) { + super(FXML); + this.module = module; + moduleCode.setText(module.getModuleCode().moduleCode); + moduleName.setText(module.getModuleName().moduleName); + + module.getInstructors().stream() + .sorted(Comparator.comparing(instructor -> instructor.getName().fullName)) + .forEach(person -> instructors.getChildren().add(new Label(person.getName().fullName))); + + cardPane.widthProperty().addListener((observable, oldValue, newValue) -> + Platform.runLater(() -> resizeInstructors(newValue.doubleValue()))); + } + + private void resizeInstructors(double width) { + instructors.setMaxWidth(width * 0.9); + instructors.setPrefWrapLength(width); + instructors.getChildren() + .filtered(x -> x instanceof Label) + .forEach(label -> resizeLabel((Label) label, width)); + } + + private void resizeLabel(Label label, double width) { + label.setMaxWidth(width * 0.4); + label.setWrapText(true); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ModuleCard)) { + return false; + } + + // state check + ModuleCard card = (ModuleCard) other; + return module.equals(card.module); + } +} diff --git a/src/main/java/seedu/address/ui/ModuleListPanel.java b/src/main/java/seedu/address/ui/ModuleListPanel.java new file mode 100644 index 00000000000..1a2b9029ee1 --- /dev/null +++ b/src/main/java/seedu/address/ui/ModuleListPanel.java @@ -0,0 +1,44 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +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.module.Module; + +public class ModuleListPanel extends UiPart { + private static final String FXML = "ModuleListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(ModuleListPanel.class); + + @javafx.fxml.FXML + private ListView moduleListView; + + /** + * Creates a {@code ModuleListPanel} with the given {@code ObservableList}. + */ + public ModuleListPanel(ObservableList moduleList) { + super(FXML); + moduleListView.setItems(moduleList); + moduleListView.setCellFactory(listView -> new ModuleListPanel.ModuleListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Module} using a {@code ModuleCard}. + */ + class ModuleListViewCell extends ListCell { + @Override + protected void updateItem(Module module, boolean empty) { + super.updateItem(module, empty); + + if (empty || module == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new ModuleCard(module, getIndex() + 1).getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java index 7fc927bc5d9..e4ad3e46b79 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCard.java @@ -2,6 +2,7 @@ import java.util.Comparator; +import javafx.application.Platform; import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.layout.FlowPane; @@ -10,7 +11,7 @@ import seedu.address.model.person.Person; /** - * An UI component that displays information of a {@code Person}. + * A UI component that displays information of a {@code Person}. */ public class PersonCard extends UiPart { @@ -35,11 +36,13 @@ public class PersonCard extends UiPart { @FXML private Label phone; @FXML - private Label address; + private Label departmentOffice; @FXML private Label email; @FXML private FlowPane tags; + @FXML + private Label remark; /** * Creates a {@code PersonCode} with the given {@code Person} and index to display. @@ -47,14 +50,32 @@ public class PersonCard extends UiPart { public PersonCard(Person person, int displayedIndex) { super(FXML); this.person = person; - id.setText(displayedIndex + ". "); + id.setText(String.format("%d ", displayedIndex)); name.setText(person.getName().fullName); phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); + departmentOffice.setText(String.format("%s, %s", person.getDepartment().value, person.getOffice().value)); email.setText(person.getEmail().value); + remark.setText(person.getRemark().value); person.getTags().stream() .sorted(Comparator.comparing(tag -> tag.tagName)) .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + + cardPane.widthProperty().addListener((observable, oldValue, newValue) -> + Platform.runLater(() -> resizeTags(newValue.doubleValue())) + ); + } + + private void resizeTags(double width) { + tags.setMaxWidth(width * 0.9); + tags.setPrefWrapLength(width); + tags.getChildren() + .filtered(x -> x instanceof Label) + .forEach(label -> resizeLabel((Label) label, width)); + } + + private void resizeLabel(Label label, double width) { + label.setMaxWidth(width * 0.4); + label.setWrapText(true); } @Override diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java index f4c501a897b..20171b166bc 100644 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ b/src/main/java/seedu/address/ui/PersonListPanel.java @@ -1,13 +1,10 @@ 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; /** @@ -15,7 +12,6 @@ */ public class PersonListPanel extends UiPart { private static final String FXML = "PersonListPanel.fxml"; - private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); @FXML private ListView personListView; diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java index 882027e4537..fc68a3edb89 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/UiManager.java @@ -20,7 +20,7 @@ public class UiManager implements Ui { public static final String ALERT_DIALOG_PANE_FIELD_ID = "alertDialogPane"; private static final Logger logger = LogsCenter.getLogger(UiManager.class); - private static final String ICON_APPLICATION = "/images/address_book_32.png"; + private static final String ICON_APPLICATION = "/images/facultype_icon.png"; private Logic logic; private MainWindow mainWindow; diff --git a/src/main/resources/images/facultype_icon.png b/src/main/resources/images/facultype_icon.png new file mode 100644 index 00000000000..6f06a99bc89 Binary files /dev/null and b/src/main/resources/images/facultype_icon.png differ diff --git a/src/main/resources/images/info_icon.png b/src/main/resources/images/info_icon.png index f8cef714095..dd371fc0fac 100644 Binary files a/src/main/resources/images/info_icon.png and b/src/main/resources/images/info_icon.png differ diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 36e6b001cd8..7310fe41841 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -1,6 +1,13 @@ +@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&display=swap'); + .background { - -fx-background-color: derive(#1d1d1d, 20%); - background-color: #383838; /* Used in the default.html file */ + -fx-background-color: #0B132B; + background-color: #0B132B; /* Used in the default.html file */ +} + +*:disabled { + -fx-opacity: 100; } .label { @@ -19,7 +26,7 @@ .label-header { -fx-font-size: 32pt; - -fx-font-family: "Segoe UI Light"; + -fx-font-family: "Poppins"; -fx-text-fill: white; -fx-opacity: 1; } @@ -29,33 +36,23 @@ -fx-font-family: "Segoe UI Semibold"; } -.tab-pane { - -fx-padding: 0 0 0 1; -} - -.tab-pane .tab-header-area { - -fx-padding: 0 0 0 0; - -fx-min-height: 0; - -fx-max-height: 0; -} - .table-view { - -fx-base: #1d1d1d; - -fx-control-inner-background: #1d1d1d; - -fx-background-color: #1d1d1d; - -fx-table-cell-border-color: transparent; - -fx-table-header-border-color: transparent; + -fx-base: #0B132B; + -fx-control-inner-background: #0B132B; + -fx-background-color: #0B132B; + -fx-table-cell-border-color: #0B132B; + -fx-table-header-border-color: #0B132B; -fx-padding: 5; } .table-view .column-header-background { - -fx-background-color: transparent; + -fx-background-color: #0B132B; } .table-view .column-header, .table-view .filler { -fx-size: 35; -fx-border-width: 0 0 1 0; - -fx-background-color: transparent; + -fx-background-color: #0B132B; -fx-border-color: transparent transparent @@ -66,7 +63,7 @@ .table-view .column-header .label { -fx-font-size: 20pt; - -fx-font-family: "Segoe UI Light"; + -fx-font-family: "Poppins"; -fx-text-fill: white; -fx-alignment: center-left; -fx-opacity: 1; @@ -77,34 +74,66 @@ } .split-pane:horizontal .split-pane-divider { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #0B132B; -fx-border-color: transparent transparent transparent #4d4d4d; } .split-pane { -fx-border-radius: 1; -fx-border-width: 1; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: transparent; } .list-view { -fx-background-insets: 0; -fx-padding: 0; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #c24848; +} + +#personListView { + -fx-background-insets: 0; + -fx-background-color: #6930c3; + -fx-background-radius: 15px; + -fx-padding: 20px 10px; +} + +#personListView .list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-padding: 3px; + -fx-background-insets: 0px, 2px; + -fx-background-color: #6930c3; } .list-cell { -fx-label-padding: 0 0 0 0; -fx-graphic-text-gap : 0; - -fx-padding: 0 0 0 0; + -fx-padding: 3px; + -fx-background-color: #c24848; +} + +#moduleListView .list-cell:filled:even { + -fx-background-color: #c24848, #8b3333; + -fx-background-radius: 15px; + -fx-background-insets: 0px, 2px; } -.list-cell:filled:even { - -fx-background-color: #3c3e3f; +#moduleListView .list-cell:filled:odd { + -fx-background-color: #c24848, #a4343d; + -fx-background-radius: 15px; + -fx-background-insets: 0px, 2px; } -.list-cell:filled:odd { - -fx-background-color: #515658; +#personListView .list-cell:filled:even { + -fx-background-color: transparent, #421785; + -fx-background-radius: 15px; + -fx-background-insets: 0px, 2px; +} + +#personListView .list-cell:filled:odd { + -fx-background-color: transparent, #381471; + -fx-background-radius: 15px; + -fx-background-insets: 0px, 2px; } .list-cell:filled:selected { @@ -113,7 +142,7 @@ .list-cell:filled:selected #cardPane { -fx-border-color: #3e7b91; - -fx-border-width: 1; + -fx-border-width: 0; } .list-cell .label { @@ -121,35 +150,36 @@ } .cell_big_label { - -fx-font-family: "Segoe UI Semibold"; - -fx-font-size: 16px; + -fx-font-family: "Poppins"; + -fx-font-weight: 700; + -fx-font-size: 18px; -fx-text-fill: #010504; } .cell_small_label { - -fx-font-family: "Segoe UI"; + -fx-font-family: "Poppins"; -fx-font-size: 13px; -fx-text-fill: #010504; } .stack-pane { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #0B132B; } .pane-with-border { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: derive(#1d1d1d, 10%); + -fx-background-color: #0B132B; + -fx-border-color: #0B132B; -fx-border-top-width: 1px; } .status-bar { - -fx-background-color: derive(#1d1d1d, 30%); + -fx-background-color: #1C2541; } .result-display { - -fx-background-color: transparent; - -fx-font-family: "Segoe UI Light"; - -fx-font-size: 13pt; + -fx-background-color: #0B132B; + -fx-font-family: "Roboto Mono Light"; + -fx-font-size: 10pt; -fx-text-fill: white; } @@ -158,15 +188,14 @@ } .status-bar .label { - -fx-font-family: "Segoe UI Light"; + -fx-font-family: "Poppins"; -fx-text-fill: white; -fx-padding: 4px; -fx-pref-height: 30px; } .status-bar-with-border { - -fx-background-color: derive(#1d1d1d, 30%); - -fx-border-color: derive(#1d1d1d, 25%); + -fx-background-color: #1C2541; -fx-border-width: 1px; } @@ -175,17 +204,25 @@ } .grid-pane { - -fx-background-color: derive(#1d1d1d, 30%); - -fx-border-color: derive(#1d1d1d, 30%); + -fx-background-color: #0B132B; + -fx-border-color: #0B132B; -fx-border-width: 1px; } +.listTitle { + -fx-font-size: 24pt; + -fx-font-family: "Poppins"; + -fx-font-weight: 700; + -fx-text-fill: white; + -fx-padding: 20px 0px 5px 15px; +} + .grid-pane .stack-pane { - -fx-background-color: derive(#1d1d1d, 30%); + -fx-background-color: #0B132B } .context-menu { - -fx-background-color: derive(#1d1d1d, 50%); + -fx-background-color: #0B132B } .context-menu .label { @@ -193,12 +230,12 @@ } .menu-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #0B132B; } .menu-bar .label { - -fx-font-size: 14pt; - -fx-font-family: "Segoe UI Light"; + -fx-font-size: 11pt; + -fx-font-family: "Poppins"; -fx-text-fill: white; -fx-opacity: 0.9; } @@ -257,11 +294,11 @@ } .dialog-pane { - -fx-background-color: #1d1d1d; + -fx-background-color: #0B132B; } .dialog-pane > *.button-bar > *.container { - -fx-background-color: #1d1d1d; + -fx-background-color: #0B132B; } .dialog-pane > *.label.content { @@ -271,7 +308,7 @@ } .dialog-pane:header *.header-panel { - -fx-background-color: derive(#1d1d1d, 25%); + -fx-background-color: #0B132B; } .dialog-pane:header *.header-panel *.label { @@ -281,13 +318,42 @@ -fx-text-fill: white; } +.scroll-pane { + -fx-background-color: #0B132B; +} + +.scroll-pane > .viewport { + -fx-background-color: #c24848; + -fx-background-radius: 15px; +} + +#personListView .scroll-pane > .viewport { + -fx-background-color: #6930c3; + -fx-background-radius: 15px; +} + +.text-area { + -fx-font-family: "Roboto Mono"; + -fx-font-size: 13pt; + -fx-text-fill: white; +} + +.text-area:focused { + -fx-background-color: transparent; + -fx-border: 0; +} + .scroll-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: transparent; } .scroll-bar .thumb { - -fx-background-color: derive(#1d1d1d, 50%); - -fx-background-insets: 3; + -fx-background-color: #1C2541; + -fx-background-insets: 6px; +} + +.module_card > .scroll-bar .thumb { + -fx-background-color: #8b3333; } .scroll-bar .increment-button, .scroll-bar .decrement-button { @@ -307,6 +373,60 @@ -fx-padding: 8 1 8 1; } +/* @author erinmayg-reused */ +.tab-pane { + -fx-background-color: #0B132B; + -fx-background-insets: 0px; + -fx-padding: 0px; +} + +.tab-pane .headers-region { + -fx-padding: 0; + -fx-background-insets: 0; +} + +.tab-pane:focused > .tab-header-area > .headers-region > .tab:selected .focus-indicator { + -fx-border-color: transparent; +} + +.tab-header-area { + -fx-padding: 0; + -fx-background-insets: 0; +} + +.tab-header-background { + -fx-background-color: #0B132B; +} + +.tab { + -fx-background-color: #8b3333; + -fx-padding: 5px 20px; + -fx-background-radius: 15px 15px 0px 0px; + -fx-focus-color: transparent; + -fx-font-size: 18px; +} + +.tab:top { + -fx-background-insets: 0; +} + +.tab:selected { + -fx-background-color: #c24848; +} + +.tab Label { + -fx-text-fill: white; + -fx-focus-color: transparent; + -fx-font-weight: bold; +} +/* @author erinmayg-reused */ + +.tab-content-area { + -fx-background-color: #c24848; + -fx-background-radius: 0px 15px 15px 15px; + padding: 15px; +} + #cardPane { -fx-background-color: transparent; -fx-border-width: 0; @@ -318,14 +438,13 @@ } #commandTextField { - -fx-background-color: transparent #383838 transparent #383838; + -fx-background-color: #1C2541; -fx-background-insets: 0; - -fx-border-color: #383838 #383838 #ffffff #383838; - -fx-border-insets: 0; - -fx-border-width: 1; - -fx-font-family: "Segoe UI Light"; + -fx-background-radius: 15px; + -fx-font-family: "Roboto Mono"; -fx-font-size: 13pt; -fx-text-fill: white; + -fx-padding: 10px 15px; } #filterField, #personListPanel, #personWebpage { @@ -333,8 +452,12 @@ } #resultDisplay .content { - -fx-background-color: transparent, #383838, transparent, #383838; - -fx-background-radius: 0; + -fx-background-color: #1C2541; + -fx-background-radius: 15px; + -fx-font-family: "Roboto Mono"; + -fx-font-size: 12pt; + -fx-text-fill: white; + -fx-padding: 10px 15px; } #tags { @@ -344,9 +467,49 @@ #tags .label { -fx-text-fill: white; - -fx-background-color: #3e7b91; - -fx-padding: 1 3 1 3; + -fx-background-color: #4ea8de; + -fx-padding: 2 8; + -fx-border-radius: 2; + -fx-background-radius: 15px; + -fx-font-size: 9pt; +} + +#instructors { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#instructors .label { + -fx-text-fill: white; + -fx-background-color: #fb8b24; + -fx-padding: 2 8; -fx-border-radius: 2; - -fx-background-radius: 2; - -fx-font-size: 11; + -fx-background-radius: 15px; + -fx-font-size: 9pt; +} + +.icon-button { + -icon-paint: white; + -fx-background-color: -icon-paint; + -size: 10; + -fx-min-height: -size; + -fx-min-width: -size; + -fx-max-height: -size; + -fx-max-width: -size; +} + +#user { + -fx-shape: "M12 12a5 5 0 1 1 0-10 5 5 0 0 1 0 10zm0-2a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm9 11a1 1 0 0 1-2 0v-2a3 3 0 0 0-3-3H8a3 3 0 0 0-3 3v2a1 1 0 0 1-2 0v-2a5 5 0 0 1 5-5h8a5 5 0 0 1 5 5v2z"; +} + +#contact { + -fx-shape: "M13.04 14.69l1.07-2.14a1 1 0 0 1 1.2-.5l6 2A1 1 0 0 1 22 15v5a2 2 0 0 1-2 2h-2A16 16 0 0 1 2 6V4c0-1.1.9-2 2-2h5a1 1 0 0 1 .95.68l2 6a1 1 0 0 1-.5 1.21L9.3 10.96a10.05 10.05 0 0 0 3.73 3.73zM8.28 4H4v2a14 14 0 0 0 14 14h2v-4.28l-4.5-1.5-1.12 2.26a1 1 0 0 1-1.3.46 12.04 12.04 0 0 1-6.02-6.01 1 1 0 0 1 .46-1.3l2.26-1.14L8.28 4z"; +} + +#department { + -fx-shape: "M8 7V5c0-1.1.9-2 2-2h4a2 2 0 0 1 2 2v2h4a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9c0-1.1.9-2 2-2h4zm8 2H8v10h8V9zm2 0v10h2V9h-2zM6 9H4v10h2V9zm4-2h4V5h-4v2z"; +} + +#email { + -fx-shape: "M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6c0-1.1.9-2 2-2zm16 3.38V6H4v1.38l8 4 8-4zm0 2.24l-7.55 3.77a1 1 0 0 1-.9 0L4 9.62V18h16V9.62z"; } diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index a431648f6c0..5cd08d5d988 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -2,19 +2,26 @@ + + - + + + + + + + - + - + @@ -33,27 +40,95 @@ - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + diff --git a/src/main/resources/view/ModuleListCard.fxml b/src/main/resources/view/ModuleListCard.fxml new file mode 100644 index 00000000000..ef0cc5e8445 --- /dev/null +++ b/src/main/resources/view/ModuleListCard.fxml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/ModuleListPanel.fxml b/src/main/resources/view/ModuleListPanel.fxml new file mode 100644 index 00000000000..31cac3dd31b --- /dev/null +++ b/src/main/resources/view/ModuleListPanel.fxml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index f08ea32ad55..4534b7b64e9 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -1,36 +1,87 @@ + - + - + - + + + - + - - - + + + diff --git a/src/main/resources/view/ResultDisplay.fxml b/src/main/resources/view/ResultDisplay.fxml index 58d5ad3dc56..4558ff64154 100644 --- a/src/main/resources/view/ResultDisplay.fxml +++ b/src/main/resources/view/ResultDisplay.fxml @@ -1,9 +1,12 @@ + - -