diff --git a/README.md b/README.md
index 13f5c77403f..0729755bc2c 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,18 @@
-[](https://github.com/se-edu/addressbook-level3/actions)
+# Productiv
+[](https://github.com/AY2021S1-CS2103T-F11-2/tp/actions)

-* 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.
+Want to be **productive** in managing your **product**?
+
+If yes, **Productiv** is the way to go!
+
+**Productiv** can help you:
+* Manage your contacts, meetings, and deliverables
+* View your meetings and deliverables on a calendar
+* Keep track of your product's progress
+
+## Site Map
+* [User Guide](https://github.com/AY2021S1-CS2103T-F11-2/tp/blob/master/docs/UserGuide.md)
+* [Developer Guide](https://github.com/AY2021S1-CS2103T-F11-2/tp/blob/master/docs/DeveloperGuide.md)
+* [About Us](https://github.com/AY2021S1-CS2103T-F11-2/tp/blob/master/docs/AboutUs.md)
diff --git a/build.gradle b/build.gradle
index be2d2905dde..a040423d351 100644
--- a/build.gradle
+++ b/build.gradle
@@ -66,7 +66,11 @@ dependencies {
}
shadowJar {
- archiveName = 'addressbook.jar'
+ archiveName = 'productiv.jar'
}
defaultTasks 'clean', 'test'
+
+run {
+ enableAssertions = true
+}
diff --git a/docs/AboutUs.md b/docs/AboutUs.md
index 1c9514e966a..fe1a7937915 100644
--- a/docs/AboutUs.md
+++ b/docs/AboutUs.md
@@ -3,57 +3,57 @@ layout: page
title: About Us
---
-We are a team based in the [School of Computing, National University of Singapore](http://www.comp.nus.edu.sg).
+Our Productiv team is based in the [School of Computing, National University of Singapore](http://www.comp.nus.edu.sg).
-You can reach us at the email `seer[at]comp.nus.edu.sg`
+You can contact us at `productiv@comp.nus.edu.sg` for any enquiries.
## Project team
-### John Doe
+### Cao Wenjie
-
+
-[[homepage](http://www.comp.nus.edu.sg/~damithch)]
-[[github](https://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](https://github.com/shadowezz)]
+[[portfolio](team/shadowezz.md)]
-* Role: Project Advisor
+* Role: Developer
+* Responsibilities: In charge of `Deliverable` features and Quality Assurance (Testing)
-### Jane Doe
+### Chrystal Quek Wan Qi
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/chrystalquek)] [[portfolio](team/chrystalquek.md)]
-* Role: Team Lead
-* Responsibilities: UI
+* Role: Developer
+* Responsibilities: In charge of "Contact" and "Mode" features, as well as Quality Assurance (Testing)
-### Johnny Doe
+### Clara Adora
-
+
-[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)]
+[[github](https://github.com/claraadora)]
+[[portfolio](team/claraadora.md)]
* Role: Developer
-* Responsibilities: Data
+* Responsibilities: In charge of "Meeting" features and Product Management
-### Jean Doe
+### Merlin Lim
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](https://github.com/MerlinLim)]
+[[portfolio](team/merlinlim.md)]
* Role: Developer
-* Responsibilities: Dev Ops + Threading
+* Responsibilities: In charge of "Meeting" features and Documentation
-### James Doe
+### Tan Chang Ri, Gabriel
-
+
-[[github](http://github.com/johndoe)]
-[[portfolio](team/johndoe.md)]
+[[github](http://github.com/gabztcr)]
+[[portfolio](team/gabztcr.md)]
* Role: Developer
-* Responsibilities: UI
+* Responsibilities: In charge of "Deliverable" features and Documentation
diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md
index 4829fe43011..8465806d93d 100644
--- a/docs/DeveloperGuide.md
+++ b/docs/DeveloperGuide.md
@@ -19,109 +19,164 @@ 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.
+The ***Architecture Diagram*** given above explains the high-level design of Productiv (referred to as "the App" or "the application"). 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-CS2103T-F11-2/tp/blob/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,
-* 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.
+<<<<<<< HEAD
+**`Main`** has two classes called [`Main`](https://github.com/AY2021S1-CS2103T-F11-2/tp/blob/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/AY2021S1-CS2103T-F11-2/tp/blob/master/src/main/java/seedu/address/MainApp.java). It is responsible for,
+* At app launch, initialising the components in the correct sequence, and connects them up with each other.
+* Upon exiting, shutting down the components and invoking cleanup methods where necessary.
-[**`Commons`**](#common-classes) represents a collection of classes used by multiple other components.
+[**`Commons`**](#common-classes) represents a collection of classes used by multiple other components.
The rest of the App consists of four components.
-* [**`UI`**](#ui-component): The UI of the App.
-* [**`Logic`**](#logic-component): The command executor.
+* [**`UI`**](#ui-component): Handles the UI of the App.
+* [**`Logic`**](#logic-component): Executes commands supplied to the App.
* [**`Model`**](#model-component): Holds the data of the App in memory.
* [**`Storage`**](#storage-component): Reads data from, and writes data to, the hard disk.
-Each of the four components,
+The app contains 3 types of entities: Deliverable, Meeting and Person
-* defines its *API* in an `interface` with the same name as the Component.
-* exposes its functionality using a concrete `{Component Name}Manager` class (which implements the corresponding API `interface` mentioned in the previous point.
+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.
+* defines its *API* using a `{Component}{Entity}` interface.
+* exposes its functionality using a concrete `{Component}{Entity}Manager` class (which implements the corresponding
+API `interface` above.)
+
+For example, the `Logic` component (see the class diagram given below) defines its API in the `Logic{Entity}.java` interface
+and exposes its functionality using the `Logic{Entity}Manager.java` class which implements the `Logic{Entity}` interface.)

**How the architecture components interact with each other**
-The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`.
-
-
+The sequence diagram below shows how the components interact with each other for the scenario where the user issues the command `delete 1` in the deliverable, meeting, or contact mode.
-The sections below give more details of each component.
+
### UI component

**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-CS2103T-F11-2/tp/blob/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`, `PersonDetailsPanel`, `CalendarListPanel`,
+ `ProjectCompletionStatusPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class.
+
+
+ The `Dashboard` parts of the UI are displayed when the application is in dashboard mode. The left side of the application consists of
+ the `ProjectCompletionStatusPanel` where the user can see the overall completion status of his/her product based on the
+ percentage of deliverables completed. The right side consists of the `CalendarListPanel` which displays a list of deliverables
+ and meetings, through `CalendarDeliverableCard` and `CalendarMeetingCard` respectively, in chronological order so that the user can
+ keep track of his/her schedule.
+
+
+ When the application is in deliverable, meeting or contact mode, the respective UI parts will be displayed. For example,
+ in deliverable mode, the left side of the application will contain the `DeliverableListPanel`, consisting of `DeliverableCard`,
+ to show the list of deliverables the user has. The right side consists of the `DeliverableDetailsPanel` which will display the full details
+ of the deliverable that the user is viewing or just performed an operation on. The same idea is applicable for meeting and contact mode.
+
+
-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/AY2021S1-CS2103T-F11-2/tp/blob/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2021S1-CS2103T-F11-2/tp/blob/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/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,
+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.
+* 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

-**API** :
-[`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java)
+**API** :
+* [`LogicDeliverable.java`](https://github.com/AY2021S1-CS2103T-F11-2/tp/tree/master/src/main/java/seedu/address/logic/LogicDeliverable.java)
+* [`LogicMeeting.java`](https://github.com/AY2021S1-CS2103T-F11-2/tp/tree/master/src/main/java/seedu/address/logic/LogicMeeting.java)
+* [`LogicPerson.java`](https://github.com/AY2021S1-CS2103T-F11-2/tp/tree/master/src/main/java/seedu/address/logic/LogicPerson.java)
+* [`LogicDispatcher.java`](https://github.com/AY2021S1-CS2103T-F11-2/tp/tree/master/src/main/java/seedu/address/logic/LogicDispatcher.java)
+
+The Logic component parses the user commands and executes them.
+`LogicDispatcher` selects the correct `LogicXYZ` based on the current mode (e.g deliverable mode).
+
+This is the list of what Model components are affected:
+* `LogicDeliverable`: Component that affects `DeliverableModel` when in deliverable mode.
+* `LogicMeeting`: Component that affects `MeetingModel` when in meeting mode.
+* `LogicPerson`: Component that affects `PersonModel` when in contact mode.
+
+Commands that do not affect Model components will be passed to `GeneralParser` when in any mode.
-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 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.
+The components follow the general sequence to execute a command:
-Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete 1")` API call.
+1. `LogicDispatcherManager` dispatches commands to
+ 1. `LogicXYZManager` class to execute and parse the user command which affect models
+ 1. `GeneralCommandParser` class to parse user commands which do not affect models
+1. This results in a `ABCCommand` object which is executed by the `LogicDispatcherManager`.
+1. The result of the command execution is encapsulated as a `CommandResult` object which is passed back to `Ui`.
+1. In addition, `CommandResult` can also instruct `Ui` to perform certain actions, such as displaying help or switching mode for the user.
-
+Given below is the Sequence Diagram for interactions within the Logic component for API call of any command.
-
: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.
+
+
+
:information_source:
+**Note:** The lifeline for `ABCCommandParser` should end at the destroy marker (X)
+but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
### Model component

-**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java)
+**API** :
+[`ModelDeliverable.java`](https://github.com/AY2021S1-CS2103T-F11-2/tp/blob/master/src/main/java/seedu/address/model/deliverable/ModelDeliverable.java),
+[`ModelMeeting.java`](https://github.com/AY2021S1-CS2103T-F11-2/tp/tree/master/src/main/java/seedu/address/model/meeting/ModelMeeting.java),
+[`ModelPerson.java`](https://github.com/AY2021S1-CS2103T-F11-2/tp/blob/master/src/main/java/seedu/address/model/person/ModelPerson.java)
-The `Model`,
+The Model component (`ModelDeliverable`, `ModelMeeting` or `ModelPerson`),
* 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 its respective deliverable, meeting, or person book.
+* exposes unmodifiable its respective `ObservableList`,`ObservableList`, or `ObservableList`.
+e.g. the UI can be bound to these lists so that the UI automatically updates when the data in the lists change.
* does not depend on any of the other three components.
+### Storage component
-
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Tag` object per unique `Tag`, instead of each `Person` needing their own `Tag` object.
-
+
-
+**API** :
+[`StorageDeliverable.java`](https://github.com/AY2021S1-CS2103T-F11-2/tp/tree/master/src/main/java/seedu/address/storage/deliverable/StorageDeliverable.java)
+[`StorageMeeting.java`](https://github.com/AY2021S1-CS2103T-F11-2/tp/tree/master/src/main/java/seedu/address/storage/meeting/StorageMeeting.java)
+[`StoragePerson.java`](https://github.com/AY2021S1-CS2103T-F11-2/tp/tree/master/src/main/java/seedu/address/storage/person/StoragePerson.java)
+The Storage component:
+* can save `UserPref` objects in JSON format and read it back.
+* can save the data in JSON format and read it back.
-### Storage component
+For saving files, storage follows this sequence:
-
+1. When `XYZBook` is updated, `StorageXYZManagers` saves the newly updated book.
+1. The newly updated book is passed to `JsonSerliazableXYZBook`.
+1. Each item in `XYZBook` is serialized by `JsonAdaptedXYZ` before overwriting the current json files.
+
+Given below is the sequence diagram for data being stored.
+
+
-**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java)
+For reading files, based on the `UserPrefs` provided, storage will find the JSON Files and load the data from there.
-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.
+
:information_source:
+**Note:**
+If the JSON files are missing or if the path is missing, a new set of JSON files will be generated with a set of sample items.
+
### Common classes
@@ -132,91 +187,297 @@ 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.
+### Date and Time Verification
-### \[Proposed\] Undo/redo feature
+#### Implementation
+The implementation allows users to parse and compare unique `DateTime` and `Time` types.
-#### 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:
+**`Time`**: To parse, `Time` should be in the following format: **`HH:mm`**
+* Single digit fields must include leading zero: `01:10`.
-* `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.
+`Time` will throw a parsing error if
+* Format is wrong (e.g missing or additional digit): `00:00:59`
+* Invalid range (e.g beyond max limit): `24:00`
-These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively.
+**`DateTime`**: To parse, `DateTime` should be in the following format: **`dd-MM-yyyy HH:mm`**
+* Single digit fields must include leading zero: `01-01-0101 01:10`.
+* Valid Calendar Range: \[`01-01-2019 00:00` - `31-12-9999 23:59`\].
-Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
+`DateTime` will throw a parsing error if
+* Format is wrong (e.g missing or additional digit): `1-10-2020 00:00:59`
+* Invalid range (e.g invalid leap year): `31-02-2020 00:00`
-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.
+DateTime can be used to compare with other DateTime objects:
+* Enable deliverables or meetings to be sorted based on which one is due the earliest.
+*Refer to [Auto-sort feature](#auto-sort-feature) to view this implementation.*
+* Ensures `From` in meeting is strictly before `To` (e.g Throw error for command `edit 1 from/01-01-2020 23:59 to/00:00` in meeting mode).
+* DateTime can be used to identify time clashes between different meetings.
-
+#### Design consideration
+* **Alternative 1 (current choice):** Throws error when invalid range is
+given for dates
+ * E.g `29-02-2019 00:00` or `31-11-2020 00:00`.
+ * Pros: Notifies user he has made a mistake.
+ * Cons: Costs time to re-type the entire command.
+
+* **Alternative 2:** Command knows how to resolve overflow of dates.
+ * E.g `29-02-2019 00:00` will be resolved automatically to `28-02-2019 00:00` the `MAX number of days of the month`.
+ * Pros: Saves time for the user if he had intended to select the last day of the month.
+ * Cons: The date specified may not be the intended input.
-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.
+### Auto-sort feature
-
+#### Implementation
-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`.
+The Auto-sort feature allows users to view `Deliverable`s, `Meeting`s, and `Person`s in a logical manner.
+Specifically, the Auto-sort feature automatically sorts `Deliverable`s, `Meeting`s, and `Person`s by the following attributes:
-
+* `Meeting` - its `From`'s `LocalDateTime` value in ascending chronological order
+* `Deliverable` - its `Deadline`'s `LocalDateTime` value in ascending chronological order
+* `Person` - its `Name`'s `String` value in ascending alphabetical order
-
: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`.
+Auto-sort is facilitated by custom classes that implements `Comparator`.
-
+The following sequence diagram shows how a list is auto-sorted upon an addition of a `Meeting`.
-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.
+
-
+#### Design consideration
-
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather
-than attempting to perform the undo.
+##### Aspect: How auto-sorting executes
-
+* **Alternative 1 (current choice):** Sorts a list upon an addition or update of an element.
+ * Pros: Error-free and easy to implement.
+ * Cons: Relatively high time complexity i.e. O(nlogn).
+* **Alternative 2:** Searches the correct index in the list to insert an element upon addition or update.
+ * Pros: Relatively low time complexity i.e. O(logn).
+ * Cons: Prone to error and difficult to implement.
+
+### Calendar feature
-The following sequence diagram shows how the undo operation works:
+#### Implementation
-
+The Calendar feature allows users to view their `Deliverable`s and`Meeting`s together in one chronologically ordered list - `calendarList`.
+Specifically, the Calendar feature combines and orders `Deliverable`s and `Meeting`s by the following attributes:
-
:information_source: **Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
+* `Meeting` - its `From`'s `LocalDateTime` value
+* `Deliverable` - its `Deadline`'s `LocalDateTime` value
-
+The combining is done by applying polymorphism; `Deliverable` and `Meeting` implement the interface `TimeEvent`.
+The following class diagram demonstrates the above-mentioned polymorphism.
+
-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.
+Meanwhile, the ordering is facilitated by the [Auto-sort feature](#auto-sort-feature).
-
: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.
+The following sequence diagram shows how the Calendar is updated upon an addition of a `Deliverable`.
+
+
+
+#### Design consideration
+
+##### Aspect: Where and how is `calendarList` updated
+
+* **Alternative 1 (current choice):** `calendarList` is in the UI component, and for any change in `UniqueDeliverableList`'s or `UniqueMeetingList`'s `internalList`:
+ 1. `calendarList` is cleared
+ 1. both `internalList`s' elements are added into `calendarList`
+ 1. `calendarList` is sorted
+ * Pros: Coupling is reduced as the implementation of `UniqueDeliverableList` and `UniqueMeetingList` are unmodified.
+ * Cons: Relatively high time complexity as any update to the `internalList`s requires clearing, adding back all `internalList`s' elements, and sorting `calendarList`.
+ This is required because `calendarList`, which is not in `UniqueDeliverableList` and `UniqueMeetingList`, has no direct access to the item being updated.
+* **Alternative 2:** `calendarList` is in `UniqueDeliverableList` and `UniqueMeetingList` as references, and for any change in `UniqueDeliverableList`'s or `UniqueMeetingList`'s `internalList`:
+ 1. `calendarList` is updated in the same way as the `internalList` involved.
+ This is possible because the `calendarList`, which is in `UniqueDeliverableList` and `UniqueMeetingList`, has direct access to the element being updated.
+ * Pros: Lower time complexity compared to Alternative 1, as both clearing and adding back all `internalList`s' elements are not needed.
+ * Cons: Coupling is increased as the implementation of `UniqueDeliverableList` and `UniqueMeetingList` are modified
+ i.e. both hold and update `calendarList` (on top of `internalList`) for any update.
+
+### Done feature
+
+#### Implementation
+
+The Done feature allows users to mark their deliverables as completed.
+
+1. The user input is received by `MainWindow` in the `UI` component before being passed to `DeliverableLogicManager` through `LogicDispatcherManager` to be executed.
+1. `DeliverableLogicManager` will call `DeliverableBookParser` which will parse the command keyword ("done") to return a `MarkDoneCommandParser`.
+1. `MarkDoneCommandParser` will then parse the command argument to return a `MarkDoneCommand`.
+1. On execution, `MarkDoneCommand` will set the status of the specified deliverable to completed and update the `ModelDeliverable` accordingly.
+
+Invalid user inputs such as an invalid index will result in the appropriate error messages displayed to the user.
+
+Given below is a sequence diagram to show how the done operation works at each step.
+
+
+
+#### Design Considerations
+
+##### Aspect: How `done` is implemented
+
+* **Alternative 1 (current choice):** Have a separate command `done` for marking deliverables as completed.
+ * Pros: Clearer and easier for the user. Prevents the `edit` command from being too cluttered with too many
+ editable fields.
+ * Cons: More code and testing required as there are additional classes created such as `MarkDoneCommand` and
+ `MarkDoneCommandParser`.
+* **Alternative 2:** Allow users to mark deliverables as completed through the existing `edit` command by changing
+the completion status field of the deliverable
+ * Pros: Less code required since we only need to make small amendments to the existing `EditCommand` and `EditCommandParser`.
+ * Cons: Format of the command will be more complex and confusing for the user. Instead of just having to pass in the index
+ of deliverable, we will need to provide a prefix (e.g. s/) and a string to represent the completion status to edit to (e.g. edit 1 s/complete).
+
+### Switch Mode feature
+
+#### Implementation
+
+The switch mode feature allows users to switch to any of the modes of the application.
+The application can be in any one of these modes: dashboard, deliverable, meeting and contact mode.
+Based on the current mode, user input is passed to the corresponding `LogicManager`,
+e.g. if the user is in deliverable mode, user input is passed to `LogicDeliverableManager`.
+Based on the current mode, the `Ui` updates with information related to that mode.
+
+The mode of the application can be switched via CLI or mouse input.
+
+Via CLI:
+1. The user input is received by the `MainWindow` in the `Ui` component and passed to `LogicDispatcherManager`.
+`LogicDispatcherManager` is the 'gatekeeper' of the Logic component.
+1. `LogicDispatcherManager` will identify the user input as a `General` command and call `GeneralParser`.
+1. `GeneralParser` will create a `SwitchCommandParser`. `SwitchCommandParser` will then parse the arguments in the user input to return a `SwitchCommand`.
+1. This `SwitchCommand` is passed back to `LogicDispatcherManager`.
+1. `LogicDispatcherManager` will then call the execute method of `SwitchCommand` which returns a `CommandResult` containing the mode that the application should switch to.
+1. This `CommandResult` is passed back to `MainWindow`.
+1. Then, `MainWindow` will retrieve the new mode from the `CommandResult`.
+1. Based on the new mode, `MainWindow` will update its own attribute `mode`.
+`MainWindow` will also update the UI to only show information related to the new mode.
+
+For the command, a `SwitchCommandParser` is implemented to parse the input into a mode.
+Invalid arguments (any argument other than `dv`, `db`, `m` and `c`) are also handled properly, with suitable error messages being displayed to the user.
+
+Given below is a sequence diagram to show how the switch mode mechanism behaves for CLI.
+
+
+
+Given below is an activity diagram to show how the switch mode operation works for CLI.
+
+
+
+
+
+
+Via mouse input:
+There is no interaction with the logic component. The only steps are:
+1. The `MainWindow` detects that a button on the navigation bar is clicked, e.g. if Deliverable is clicked, `switchDeliverable` method of `MainWindow` is called.
+1. The `MainWindow` will update its own attribute `mode` and the UI to only show information related to the new mode.
+
+Given below is a sequence diagram to show how the switch mode mechanism behaves for mouse input.
+
+
+The two sequence diagrams are separated for simplicity
+
+
+#### Design consideration
+
+##### Aspect: How Switch commands should be implemented
+
+* **Alternative 1 (current choice):** Shortened user commands: `switch` `db`, `dv`, `m` or `c`.
+ * Pros: More convenient and faster to type shorter user commands.
+ * Cons: More difficult for users to remember short forms.
+
+* **Alternative 2 (original implementation):** Longer user commands: `switch` `dashboard`, `deliverable`, `meeting` or `contact`.
+ * Pros: Clearer as commands correspond to the naming of tabs on the navigation bar.
+ * Cons: Takes longer to type longer user commands.
+
+##### Aspect: Where mode is stored
+
+* **Alternative 1 (current choice):** Store mode in `MainWindow`.
+ * Pros: `MainWindow` can update UI easily by accessing current mode.
+ * Cons: Need to keep passing current mode to `LogicDispatcherManager`.
+
+* **Alternative 2:** Store mode in both `MainWindow` and `LogicDispatcherManager`.
+ * Pros: Easier implementation. No need to keep passing current mode to `LogicDispatcherManager`.
+ * Cons: No single source of truth, could lead to bugs.
-
-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.
+### View feature
-
+#### Implementation
-Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …` command. This is the behavior that most modern desktop applications follow.
+The view feature allows users to view the details of a specific deliverable, meeting or contact on the right
+panel of the display window, depending on the mode the application is in.
-
+1. Suppose the user in currently in the meeting mode, the user input received by `MainWindow` in `UI` component will be passed to `MeetingLogicManager` through `LogicDispatcherManager` to be executed.
+1. `MeetingLogicManager` will call `MeetingBookParser` which will parse the command keyword ("view") to return a `ViewCommandParser`.
+1. `ViewCommandParser` will then parse the command argument to return a `ViewCommand`.
+1. On execution, `ViewCommand` will update the `ModelMeeting` to set the meeting currently in view.
+1. `UI` component will then make a separate call to `ModelMeeting` to retrieve the meeting currently in view and display its full details to the user in the right panel of the application.
-The following activity diagram summarizes what happens when a user executes a new command:
+Invalid user inputs such as an invalid index will result in the appropriate error messages displayed to the user.
-
+The following sequence diagram shows how the view operation works in each step:
-#### Design consideration:
+
-##### Aspect: How undo & redo executes
+#### Design consideration
-* **Alternative 1 (current choice):** Saves the entire address book.
- * 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.
+##### Aspect: How view executes
-_{more aspects and alternatives to be added}_
+* **Alternative 1 (current choice):** Stores the item in view inside the respective model.
+ * Pros: Ensures persistence as it can be referred to repeatedly.
+ * Cons: Requires another operation to fetch the item in view to be displayed.
-### \[Proposed\] Data archiving
+* **Alternative 2:** Passes the item in view inside the Command Result to the UI component
+ * Pros: Does not require an additional operation to fetch the item in view.
+ * Cons: Cluttering of Command Result object which now needs to store mode-specific items. This is against its original purpose
+ which is to pass mode-neutral information, such as error messages, back to UI for display after a command execution.
-_{Explain here how the data archiving feature will be implemented}_
+### Overall Completion Percentage feature
+#### Implementation
+
+The Overall Completion Percentage (OCP) feature is located at the left panel of the dashboard in Productiv.
+It is a donut chart implemented with the third-party library [fx-progress-circle](https://github.com/torakiki/fx-progress-circle/),
+and it appears as shown below.
+
+
+
+The OCP feature allows product managers to have a quick overview of the progress of their product's development
+so that they can work better towards production deadlines.
+OCP is given by the
+formula*:
+
+
+**_OCP (%) = Number of Completed Deliverables / Total Number of Deliverables × 100_**
+
+
+\* If no deliverables exist, OCP will be set to **0%**.
+
+The OCP will be updated upon switching to dashboard mode.
+This can be done via CLI (with the command `switch db`) or mouse input (clicking on the dashboard tab).
+
+The following steps and sequence diagram shows how the **updating** of the OCP is implemented via mouse input.
+You should note the mechanism after the mode has been switched.
+
+1. `MainWindow` detects that the dashboard button in the navigation bar is clicked, and its `switchDashboard` method is called.
+2. `MainWindow` updates its own attribute `mode` to reflect the dashboard and its UI displays it accordingly.
+3. The `updateOcp` method of `ProjectCompletionStatusPanel` is called.
+4. `ProjectCompletionStatusPanel` calls the `size` method of its `deliverableList` (of type `ObservableList`), which returns the total number of deliverables (`totalNumDeliverables`).
+5. `ProjectCompletionStatusPanel` self-invokes its `findNumCompletedDeliverables` method that returns the number of completed deliverables of its `deliverableList` (`numCompletedDeliverables`).
+6. `ProjectCompletionStatusPanel` self-invokes its `getOcp` method with `totalNumDeliverables` and `numCompletedDeliverables` as parameters, and returns the `overallCompletionPercentage`*.
+
+*This value is later passed to the progress ring indicator to render the OCP donut chart in the dashboard.
+
+
+
+#### Design consideration
+
+##### Aspect: How updating of OCP executes
+
+* **Alternative 1 (current choice):** Calculate OCP on-the-go only upon switching to dashboard mode.
+ * Pros: Less prone to calculation errors.
+ * Cons: Takes slightly longer to calculate OCP for display.
+* **Alternative 2:** Have a global variable for OCP that updates upon any relevant change to the deliverable list.
+ * Pros: Potentially faster to retrieve OCP information for display (using a getter method).
+ * Cons: More prone to errors from higher frequency of calculation.
--------------------------------------------------------------------------------------------------------------------
@@ -230,86 +491,724 @@ _{Explain here how the data archiving feature will be implemented}_
--------------------------------------------------------------------------------------------------------------------
-## **Appendix: Requirements**
-
-### Product scope
+## **Appendix A: Product Scope**
**Target user profile**:
* has a need to manage a significant number of contacts
-* prefer desktop apps over other types
+* has a need to manage a meeting schedule
+* has a need to oversee the development of a product
+* prefers to have product-related information in a single application
+* prefer desktop apps over other types of apps
* 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**:
+* consolidates product-related information such as deliverables, meetings and contacts into one place
+* manage their product’s development more comprehensively and conveniently than a typical mouse/GUI driven app
+
+
+## **Appendix B: User Stories**
+
+Priority Legend:
+* `* * *` - High (must be addressed)
+* `* *` - Medium (would be nice to address)
+* `*` - Low (least likely to be addressed)
+
+| Priority | As a ... | I can ... | So that I can ... |
+| ---------- | ------------------------------------------ | --------------------------------------------------------------------------- | ---------------------------------------------------------- |
+| **EPIC A** | Product Manager | track my product’s deliverables | work better towards meeting them |
+| `* * *` | Product Manager | add deliverables for the product | keep track of them |
+| `* * *` | Product Manager | mark deliverables as completed | know which ones I've done |
+| `* * *` | Product Manager | delete deliverables that are no longer relevant | focus on other deliverables |
+| `* *` | Product Manager | edit the details of the deliverables | keep them updated |
+| `* *` | Product Manager | tag deliverables to different milestones | distinguish the deliverables easily |
+| `* *` | Product Manager | add contacts under my deliverables | know who is involved in meeting the deliverable |
+| `* *` | Product Manager | edit a deliverable which was wrongly marked done back to its original status| ensure my deliverables are reflected correctly |
+| `* *` | Product Manager with many deliverables | search for specific ones | easily find them from my entire list |
+| `* *` | Product Manager with many deliverables | have my deliverables sorted chronologically by deadline | look out for more urgent deliverables |
+| `* *` | Product Manager | be informed when a deliverable is close to its deadline or has passed it | know which deliverables need more attention |
+| **EPIC B** | Product Manager | manage my product-related meetings | be clear on my meeting schedule |
+| `* * *` | Product Manager | add new meetings that I'm scheduled for | keep track of them |
+| `* * *` | Product Manager | delete my scheduled meetings | remove outdated or cancelled meetings |
+| `* *` | Product Manager | edit the details of my scheduled meetings | keep them updated |
+| `* *` | Product Manager | add contacts and location under my meetings | know who is involved and where the meeting is taking place |
+| `* *` | Product Manager with many meetings | search for specific ones | easily find them from my entire list |
+| `* *` | Product Manager with many meetings | have my meetings sorted chronologically by its start time | look out for the earlier meetings that were scheduled |
+| `* *` | Product Manager | be informed when a meeting is close to starting, on-going or has ended | be updated on the status of my meetings |
+| **EPIC C** | Product Manager | organise my developer or stakeholder contacts | reference them easily |
+| `* * *` | Product Manager | add contacts related to the product | store their details for future communication |
+| `* * *` | Product Manager | distinguish between developers and stakeholders in a project easily | remember their respective roles |
+| `* * *` | Product Manager | delete contacts that are no longer relevant | forget about unimportant contacts |
+| `* *` | Product Manager | edit the details of my contacts | keep them updated |
+| `* *` | Product Manager with many contacts | search for specific ones | easily find them from my entire list |
+| `* *` | Product Manager with many contacts | have my contacts sorted by alphabetical order | keep my contact list organised |
+| **EPIC D** | Product Manager | have an overview of my product's development and upcoming events | work better towards production deadlines |
+| `* *` | Product Manager | view the overall completion of the product | know the current progress of the product's development |
+| `* *` | Product Manager | see a calendar view of my deliverables and meetings | know which product-related events are upcoming and urgent |
+| `*` | Product Manager | toggle between daily, weekly and monthly view of the calendar | have a variety of views to see my upcoming events |
+| **EPIC E** | forgetful or inexperienced Product Manager | view app instructions and tips | be able to use it as intended |
+| `* * *` | Product Manager | receive feedback from the app | know the system has successfully registered my action |
+| `* *` | Product Manager | get directions to the app's user guide | easily access the relevant instructions |
+| `*` | Product Manager | view a shortcut reference | receive technical solutions immediately for command issues |
+
+## **Appendix C: Use Cases**
+
+For all use cases below, the **System** is `Productiv` and the **Actor** is the `user`.
+
+### General
+
+**Use case: G01 - Switch Mode**
+**MSS**
+1. User chooses to switch to another mode.
+2. User enters the command to switch mode into the command box.
+3. Productiv switches to the expected mode and displays a success message in the feedback box.
+
+ Use case ends.
+
+**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * 2a3. Steps 2a1-2a2 are repeated until the command entered is correct.
+
+ Use case resumes from step 3.
-### User stories
+**Use case: G02 - Help**
-Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*`
+**Precondition(s):**
+* User has a stable internet connection.
-| 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 |
+**MSS**
+1. User chooses to view instructions on how to use Productiv.
+2. User enters the help command into the command box.
+3. Productiv shows a help window with a copyable URL to its user guide and displays a success message in the feedback box.
+4. User copies the link into their browser and is directed to the user guide.
-*{More to be added}*
+ Use case ends.
-### Use cases
+**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * 2a3. Steps 2a1-2a2 are repeated until the command entered is correct.
-(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise)
+ Use case resumes from step 3.
-**Use case: Delete a person**
+**Use case: G03 - Exit**
**MSS**
+1. User chooses to exit Productiv.
+2. User enters the exit command into the command box.
+3. Productiv closes.
-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
+ Use case ends.
+
+**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * 2a3. Steps 2a1-2a2 are repeated until the command entered is correct.
+
+ Use case resumes from step 3.
+
+### Deliverable
+
+**Use case: D01 - Add a deliverable**
+
+**Precondition(s):**
+* User is in the deliverable mode.
+
+**Guarantee(s):**
+* The dashboard's OCP and Schedule will be updated accordingly.
+* The added deliverable will be reflected in the left and right panels.
+
+**MSS**
+1. User chooses to add a deliverable.
+2. User enters the command to add a deliverable into the command box.
+3. Productiv adds the deliverable into the deliverable list and displays a success message in the feedback box.
Use case ends.
**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * Steps 2a1-2a2 are repeated until the command entered is correct.
+
+ Use case resumes from step 3.
+
+**Use case: D02 - Edit a deliverable**
+
+**Precondition(s):**
+* User is in the deliverable mode.
+* Deliverable to edit exists.
+
+**Guarantee(s):**
+* The dashboard's Schedule will be updated accordingly.
+* The edited deliverable will be reflected in the left and right panels.
-* 2a. The list is empty.
+**MSS**
+1. User chooses to edit a deliverable.
+2. User enters the command to edit a deliverable into the command box.
+3. Productiv edits the deliverable accordingly and displays a success message in the feedback box.
+
+ Use case ends.
+
+**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * Steps 2a1-2a2 are repeated until the command entered is correct.
+
+ Use case resumes from step 3.
+
+**Use case: D03 - Mark a deliverable as completed**
- Use case ends.
+**Precondition(s):**
+* User is in the deliverable mode.
+* Deliverable to mark as completed exists.
-* 3a. The given index is invalid.
+**Guarantee(s):**
+* The dashboard's OCP and Schedule will be updated accordingly.
+* The deliverable marked as completed will be reflected in the left and right panels.
- * 3a1. AddressBook shows an error message.
+**MSS**
+1. User chooses to mark a deliverable as completed.
+2. User enters the command to mark a deliverable as completed into the command box.
+3. Productiv marks the deliverable as completed and displays a success message in the feedback box.
- Use case resumes at step 2.
+ Use case ends.
-*{More to be added}*
+**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * Steps 2a1-2a2 are repeated until the command entered is correct.
+
+ Use case resumes from step 3.
+
+**Use case: D04 - Mark a deliverable as on-going**
+
+**Precondition(s):**
+* User is in the deliverable mode.
+* Deliverable to mark as on-going exists.
+
+**Guarantee(s):**
+* The dashboard's OCP and Schedule will be updated accordingly.
+* The deliverable marked as on-going will be reflected in the left and right panels.
-### Non-Functional Requirements
+**MSS**
+1. User chooses to mark a deliverable as on-going.
+2. User enters the command to mark a deliverable as on-going into the command box.
+3. Productiv marks the deliverable as on-going and displays a success message in the feedback box.
-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.
+ Use case ends.
-*{More to be added}*
+**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * Steps 2a1-2a2 are repeated until the command entered is correct.
+
+ Use case resumes from step 3.
+
+**Use case: D05 - View a deliverable**
+
+**Precondition(s):**
+* User is in the deliverable mode.
+* Deliverable to view exists.
-### Glossary
+**MSS**
+1. User chooses to view a deliverable.
+2. User enters the command to view a deliverable into the command box.
+3. Productiv displays the deliverable in the right panel and displays a success message in the feedback box.
-* **Mainstream OS**: Windows, Linux, Unix, OS-X
-* **Private contact detail**: A contact detail that is not meant to be shared with others
+ Use case ends.
---------------------------------------------------------------------------------------------------------------------
+**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * Steps 2a1-2a2 are repeated until the command entered is correct.
+
+ Use case resumes from step 3.
+
+**Use case: D06 - Find deliverables**
+
+**Precondition(s):**
+* User is in the deliverable mode.
+* User has keyword(s) for Productiv to find deliverables with.
+
+**Guarantee(s):**
+* The right panel will be cleared.
-## **Appendix: Instructions for manual testing**
+**MSS**
+1. User chooses to find deliverables.
+2. User enters the command and keyword(s) to find deliverables into the command box.
+3. Productiv returns all matching deliverables in the left panel and displays a success message in the feedback box.
+
+ Use case ends.
+
+**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * Steps 2a1-2a2 are repeated until the command entered is correct.
+
+ Use case resumes from step 3.
+
+**Use case: D07 - List all deliverables**
+
+**Precondition(s):**
+* User is in the deliverable mode.
+* User has at least one existing deliverable in Productiv.
+
+**Guarantee(s):**
+* The right panel will be cleared.
+
+**MSS**
+1. User chooses to list all deliverables.
+2. User enters the command to list all deliverables into the command box.
+3. Productiv lists out all deliverables in the left panel and displays a success message in the feedback box.
+
+ Use case ends.
+
+**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * Steps 2a1-2a2 are repeated until the command entered is correct.
+
+ Use case resumes from step 3.
+
+**Use case: D08 - Delete a deliverable**
+
+**Precondition(s):**
+* User is in the deliverable mode.
+* Deliverable to delete exists.
+
+**Guarantee(s):**
+* The left panel will reflect the updated deliverable list.
+* The right panel will be cleared.
+* The dashboard's OCP and Schedule will be updated accordingly.
+* The deleted deliverable will not have its data stored anymore in Productiv.
+* The deleted deliverable cannot be retrieved back.
+
+**MSS**
+1. User chooses to delete a deliverable.
+2. User enters the command to delete a deliverable into the command box.
+3. Productiv deletes the deliverable from the deliverable list and displays a success message in the feedback box.
+
+ Use case ends.
+
+**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * Steps 2a1-2a2 are repeated until the command entered is correct.
+
+ Use case resumes from step 3.
+
+**Use case: D09 - Clear all deliverables**
+
+**Precondition(s):**
+* User is in the deliverable mode.
+* User has at least one existing deliverable in Productiv.
+
+**Guarantee(s):**
+* The left and right panels will be cleared.
+* The dashboard's OCP and Schedule will be updated accordingly.
+* The cleared deliverables will not have its data stored anymore in Productiv.
+* The cleared deliverables cannot be retrieved back.
+
+**MSS**
+1. User chooses to clear all deliverables.
+2. User enters the command to clear all deliverables into the command box.
+3. Productiv clears all deliverables from the deliverable list and displays a success message in the feedback box.
+
+ Use case ends.
+
+**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * Steps 2a1-2a2 are repeated until the command entered is correct.
+
+ Use case resumes from step 3.
+
+### Meeting
+
+**Use case: M01 - Add a meeting**
+
+**Precondition(s):**
+* User is in the meeting mode.
+
+**Guarantee(s):**
+* The dashboard's Schedule will be updated accordingly.
+* The added meeting will be reflected in the left and right panels.
+
+**MSS**
+1. User chooses to add a meeting.
+2. User enters the command to add a meeting into the command box.
+3. Productiv adds the meeting into the meeting list and displays a success message in the feedback box.
+
+ Use case ends.
+
+**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * Steps 2a1-2a2 are repeated until the command entered is correct.
+
+ Use case resumes from step 3.
+
+**Use case: M02 - Edit a meeting**
+
+**Precondition(s):**
+* User is in the meeting mode.
+* Meeting to edit exists.
+
+**Guarantee(s):**
+* The dashboard's Schedule will be updated accordingly.
+* The edited meeting will be reflected in the left and right panels.
+
+**MSS**
+1. User chooses to edit a meeting.
+2. User enters the command to edit a meeting into the command box.
+3. Productiv edits the meeting accordingly and displays a success message in the feedback box.
+
+ Use case ends.
+
+**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * Steps 2a1-2a2 are repeated until the command entered is correct.
+
+ Use case resumes from step 3.
+
+**Use case: M03 - View a meeting**
+
+**Precondition(s):**
+* User is in the meeting mode.
+* Meeting to view exists.
+
+**MSS**
+1. User chooses to view a meeting.
+2. User enters the command to view a meeting into the command box.
+3. Productiv displays the meeting in the right panel and displays a success message in the feedback box.
+
+ Use case ends.
+
+**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * Steps 2a1-2a2 are repeated until the command entered is correct.
+
+ Use case resumes from step 3.
+
+**Use case: M04 - Find meetings**
+
+**Precondition(s):**
+* User is in the meeting mode.
+* User has keyword(s) for Productiv to find meetings with.
+
+**Guarantee(s):**
+* The right panel will be cleared.
+
+**MSS**
+1. User chooses to find meetings.
+2. User enters the command and keyword(s) to find meetings into the command box.
+3. Productiv returns all matching meetings in the left panel and displays a success message in the feedback box.
+
+ Use case ends.
+
+**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * Steps 2a1-2a2 are repeated until the command entered is correct.
+
+ Use case resumes from step 3.
+
+**Use case: M05 - List all meetings**
+
+**Precondition(s):**
+* User is in the meeting mode.
+* User has at least one existing meeting in Productiv.
+
+**Guarantee(s):**
+* The right panel will be cleared.
+
+**MSS**
+1. User chooses to list all meetings.
+2. User enters the command to list all meetings into the command box.
+3. Productiv lists out all meetings in the left panel and displays a success message in the feedback box.
+
+ Use case ends.
+
+**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * Steps 2a1-2a2 are repeated until the command entered is correct.
+
+ Use case resumes from step 3.
+
+**Use case: M06 - Delete a meeting**
+
+**Precondition(s):**
+* User is in the meeting mode.
+* Meeting to delete exists.
+
+**Guarantee(s):**
+* The left panel will reflect the updated meeting list.
+* The right panel will be cleared.
+* The dashboard's Schedule will be updated accordingly.
+* The deleted meeting will not have its data stored anymore in Productiv.
+* The deleted meeting cannot be retrieved back.
+
+**MSS**
+1. User chooses to delete a meeting.
+2. User enters the command to delete a meeting into the command box.
+3. Productiv deletes the meeting from the meeting list and displays a success message in the feedback box.
+
+ Use case ends.
+
+**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * Steps 2a1-2a2 are repeated until the command entered is correct.
+
+ Use case resumes from step 3.
+
+**Use case: M07 - Clear all meetings**
+
+**Precondition(s):**
+* User is in the meeting mode.
+* User has at least one existing meeting in Productiv.
+
+**Guarantee(s):**
+* The left and right panels will be cleared.
+* The dashboard's Schedule will be updated accordingly.
+* The cleared meetings will not have its data stored anymore in Productiv.
+* The cleared meetings cannot be retrieved back.
+
+**MSS**
+1. User chooses to clear all meetings.
+2. User enters the command to clear all meetings into the command box.
+3. Productiv clears all meetings from the meeting list and displays a success message in the feedback box.
+
+ Use case ends.
+
+**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * Steps 2a1-2a2 are repeated until the command entered is correct.
+
+ Use case resumes from step 3.
+
+### Contact
+
+**Use case: C01 - Add a contact**
+
+**Precondition(s):**
+* User is in the contact mode.
+
+**Guarantee(s):**
+* The added contact will be reflected in the left and right panels.
+
+**MSS**
+1. User chooses to add a contact.
+2. User enters the command to add a contact into the command box.
+3. Productiv adds the contact into the contact list and displays a success message in the feedback box.
+
+ Use case ends.
+
+**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * Steps 2a1-2a2 are repeated until the command entered is correct.
+
+ Use case resumes from step 3.
+
+**Use case: C02 - Edit a contact**
+
+**Precondition(s):**
+* User is in the contact mode.
+* Contact to edit exists.
+
+**Guarantee(s):**
+* The edited contact will be reflected in the left and right panels.
+
+**MSS**
+1. User chooses to edit a contact.
+2. User enters the command to edit a contact into the command box.
+3. Productiv edits the contact accordingly and displays a success message in the feedback box.
+
+ Use case ends.
+
+**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * Steps 2a1-2a2 are repeated until the command entered is correct.
+
+ Use case resumes from step 3.
+
+**Use case: C03 - View a contact**
+
+**Precondition(s):**
+* User is in the contact mode.
+* Contact to view exists.
+
+**MSS**
+1. User chooses to view a contact.
+2. User enters the command to view a contact into the command box.
+3. Productiv displays the contact in the right panel and displays a success message in the feedback box.
+
+ Use case ends.
+
+**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * Steps 2a1-2a2 are repeated until the command entered is correct.
+
+ Use case resumes from step 3.
+
+**Use case: C04 - Find contacts**
+
+**Precondition(s):**
+* User is in the contact mode.
+* User has keyword(s) for Productiv to find contacts with.
+
+**Guarantee(s):**
+* The right panel will be cleared.
+
+**MSS**
+1. User chooses to find contacts.
+2. User enters the command and keyword(s) to find contacts into the command box.
+3. Productiv returns all matching contacts in the left panel and displays a success message in the feedback box.
+
+ Use case ends.
+
+**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * Steps 2a1-2a2 are repeated until the command entered is correct.
+
+ Use case resumes from step 3.
+
+**Use case: C05 - List all contacts**
+
+**Precondition(s):**
+* User is in the contact mode.
+* User has at least one existing contact in Productiv.
+
+**Guarantee(s):**
+* The right panel will be cleared.
+
+**MSS**
+1. User chooses to list all contacts.
+2. User enters the command to list all contacts into the command box.
+3. Productiv lists out all contacts in the left panel and displays a success message in the feedback box.
+
+ Use case ends.
+
+**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * Steps 2a1-2a2 are repeated until the command entered is correct.
+
+ Use case resumes from step 3.
+
+**Use case: C06 - Delete a contact**
+
+**Precondition(s):**
+* User is in the contact mode.
+* Contact to delete exists.
+
+**Guarantee(s):**
+* The left panel will reflect the updated contact list.
+* The right panel will be cleared.
+* The deleted contact will not have its data stored anymore in Productiv.
+* The deleted contact cannot be retrieved back.
+
+**MSS**
+1. User chooses to delete a contact.
+2. User enters the command to delete a contact into the command box.
+3. Productiv deletes the contact from the contact list and displays a success message in the feedback box.
+
+ Use case ends.
+
+**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * Steps 2a1-2a2 are repeated until the command entered is correct.
+
+ Use case resumes from step 3.
+
+**Use case: C07 - Clear all contacts**
+
+**Precondition(s):**
+* User is in the contact mode.
+* User has at least one existing contact in Productiv.
+
+**Guarantee(s):**
+* The left and right panels will be cleared.
+* The cleared contacts will not have its data stored anymore in Productiv.
+* The cleared contacts cannot be retrieved back.
+
+**MSS**
+1. User chooses to clear all contacts.
+2. User enters the command to clear all contacts into the command box.
+3. Productiv clears all contacts from the contact list and displays a success message in the feedback box.
+
+ Use case ends.
+
+**Extensions**
+* 2a. Productiv detects an error in the command.
+ * 2a1. Productiv displays an error message in the feedback box.
+ * 2a2. User enters the command again.
+ * Steps 2a1-2a2 are repeated until the command entered is correct.
+
+ Use case resumes from step 3.
+
+## **Appendix D: Non-Functional Requirements**
+
+1. Should work on any [`mainstream OS`](#appendix-e-glossary) as long as Java `11` is installed and is the default.
+1. Should be able to hold up to 1000 deliverables, 1000 meetings and 1000 contacts without noticeable sluggishness in performance for typical usage.
+1. 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. This excludes the Switch and Help command.
+1. The user interface should look intuitive and simple to navigate. It should not look cluttered with too many panels.
+1. The application should be for a single user, with its size being smaller than 100MB.
+
+## **Appendix E: Glossary**
+
+* **Deliverable**: An item to be completed as part of the product development process.
+* **Mainstream OS**: Windows, Unix, OS-X.
+* **Milestone**: A stage in the software development process associated with a particular group of deliverables.
+* **Mode**: The state of the application that affects how each command will be executed. The app can be in dashboard, deliverable, meeting or contact mode.
+* **OCP**: Overall Completion Percentage. It is a donut chart showing the project's completion status, found on the left panel of the Dashboard.
+* **Role**: A function assumed or part played by a `Contact`/`Person`, who is either a developer or stakeholder.
+* **Stakeholder**: An external party involved with the product.
+
+## **Appendix F: 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.
+testers are expected to do more *exploratory* testing. Each test case is to be executed independently of each other.
@@ -317,40 +1216,124 @@ testers are expected to do more *exploratory* testing.
1. Initial launch
- 1. Download the jar file and copy into an empty folder
+ 1. Prerequisite: You have Java `11` installed in your computer (it should be your default Java version).
- 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
+ 1. Test case: Download the .jar file from [here](https://github.com/AY2021S1-CS2103T-F11-2/tp/releases) and copy into an empty folder.
+ From the terminal, navigate to the folder containing the .jar file and enter `java -jar productiv.jar` to start *Productiv*.
+ Expected: Shows the GUI with a dashboard containing some sample data. 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.
+ 1. Test case: Resize the window to an optimum size. Move the window to a different location. Close the window. Re-launch the app.
Expected: The most recent window size and location is retained.
-1. _{ more test cases … }_
+
:information_source: **Note:** The window has a minimum width and height so that the UI does not look so cramped.
+
+
+1. Shutting down
+
+ 1. Test case: Click the window close button.
+ Expected: The app shuts down.
+
+ 1. Test case: `exit`
+ Expected: Similar to previous.
+
+### Switching Modes
+
+1. Switching to deliverable mode
+
+ 1. Prerequisite: You are not in deliverable mode.
-### Deleting a person
+ 1. Test case: Click `Deliverable` on the top navigation bar.
+ Expected: Window displays list of saved deliverables.
-1. Deleting a person while all persons are being shown
+ 1. Test case: `switch dv`
+ Expected: Similar to previous.
- 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list.
+ 1. Other incorrect switch commands to try: `switch meeting`, `switch dev`
+ Expected: Status bar throws error message.
- 1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated.
+### Adding a deliverable
- 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.
+1. Adding Login screen
- 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
- Expected: Similar to previous.
+ 1. Prerequisites: You are in deliverable mode. The deliverable `Login screen` is not already added. If it is already added, delete it.
-1. _{ more test cases … }_
+ 1. Test case: `add t/Login screen by/10-10-2020 18:00 m/1.1 c/Jordan Woods, Betsy Crowe d/Include email and password fields`
+ Expected: The deliverable `Login screen` is added to the list of deliverables and is expanded in the right panel.
+
+ 1. Test case: `add t/Login screen`
+ Expected: No deliverable is added. Status bar throws error message.
+
+ 1. Other incorrect add commands to try: `add`, `add Login screen`
+ Expected: Status bar throws error message.
### Saving data
-1. Dealing with missing/corrupted data files
+1. Deliverables, meetings and contacts are saved automatically to ./data/.
+
+ On normal usage, data is saved to 3 JSON files - `deliverablebook.json`, `meetingbook.json` and `contactbook.json`.
+ All 3 files contain information stored by the user from the respective modes.
+
+ 1. Prerequisites: Very first time using the application. Delete all files under ./data/ if not the first time using the application.
+
+ 1. Test case: Start and close the app immediately.
+ Expected: The 3 JSON files are not created.
+
+ 1. Test case: Start the app. Switch to deliverable mode. Enter `list`. Close the app.
+ Expected: Of the 3 JSON files, only `deliverablebook.json` created.
+
+1. Dealing with missing or corrupted data files
+
+ 1. Test case: Delete `deliverablebook.json` file. Start the app. Switch to deliverable mode. Enter `list`. Close the app.
+ Expected: `deliverablebook.json` should re-initialise a list of sample deliverables.
+
+ 1. Test case: Corrupt `deliverablebook.json` under ./data/. Add a (`-`) to a saved deliverable's milestone.
+ Expected: The app should be able to start up but show no deliverables.
+
+
:information_source: **Note:** To re-initialise a list of sample deliverables, execute the previous test case.
+
+
+
+## **Appendix G: Effort**
+
+| Feature | AB3 | Productiv |
+| ----------- | ------- | ------------ |
+| LoC | ~9k | ~20k |
+| Difficulty | 10 | 15 |
+| Effort | 10 | 15 |
+
+
+
+**Understanding our target user profile**
+
+Initially, we had completely different ideas on what our target user profile is. We were confused about the differences between product owners, product managers, business analysts and project leads.
+
+To ensure that we were all on the same page, we made sure to talk things out before starting our project. We researched on the job scope of a product manager and shared with each other our experiences of working in different organisations and what product managers do at these organisations.
+
+Eventually, our shared understanding on our target user profile helped us to build a cohesive product catered to product managers.
+
+
+**Model**
+
+The `Model` of Productiv is certainly more complex than that of AddressBook. In AddressBook, there was only one key entity type in play - `Person`. For Productiv, three different entity types are managed at once - `Deliverable`, `Meeting` and `Person`.
+
+As such, we had to restructure our entire application to accommodate these three entity types. Throughout the project, we had to rethink and refactor the structure of our code, weighing the pros and cons of each approach. This was a very painful process and also vulnerable to regressions.
+
+Eventually, we separated the models into three different `ModelManager`s, handled by three different `LogicManager`s, adhering to the Separation of Concerns Principle. The reduced coupling decreased the dependencies between the models.
+
+This also influenced our decision to not link the `Contacts` field in `Deliverable` and `Meeting` to data in the `Person` model. This also provided greater flexibility to users as they could add contacts to `Deliverable`s and `Meeting`s without recording the details of the contact, e.g. a `Meeting` can involve people who are not important to record as a `Person`.
+
+
+**Ui**
+
+The `Ui` of Productiv was almost entirely revamped from the AddressBook.
+
+The easiest way would have been to stick to the current `Ui` of the AddressBook i.e. have 3 lists (`DeliverableListPanel`, `MeetingListPanel` and `PersonListPanel`) on the same page. While this would have been easier to implement, it would have made Productiv look very cluttered. We chose the hard way as we believe in making the user-experience seamless and enjoyable. As such, we worked hard to have the `Ui` change according to the current mode the user is in and also create an entirely new View Panel to enhance the user experience.
+
+The `Dashboard` was difficult to implement. In particular, the OCP was definitely not something that could be done overnight. None of us had experience with JavaFX prior to CS2103T. We did extensive research on the libraries that we could use and exhaustive checks to ensure that the OCP was synced with the rest of Productiv. Eventually, we managed to create the OCP, which vastly improved the user experience.
+
- 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_
+**Overall**
-1. _{ more test cases … }_
+As a whole, this process was fraught with challenges. Whenever we had to face obstacles, we worked with each other to brainstorm and decide on the best solution. We made sure that everyone followed the same workflow and reviewed each other’s work to maintain the code quality of our codebase. We have learnt alot from each other, beyond just technical skills. Productiv would not have been possible without the hard work and commitment of the entire team.
diff --git a/docs/SettingUp.md b/docs/SettingUp.md
index 77667c6d581..d18390acc92 100644
--- a/docs/SettingUp.md
+++ b/docs/SettingUp.md
@@ -19,7 +19,7 @@ Follow the steps in the following guide precisely. Things will not work out if y
First, **fork** this repo, and **clone** the fork into your computer.
If you plan to use Intellij IDEA (highly recommended):
-1. **Configure the JDK**: Follow the guide [_[se-edu/guides] IDEA: Configuring the JDK_](https://se-education.org/guides/tutorials/intellijJdk.html) to to ensure Intellij is configured to use **JDK 11**.
+1. **Configure the JDK**: Follow the guide [_[se-edu/guides] IDEA: Configuring the JDK_](https://se-education.org/guides/tutorials/intellijJdk.html) to ensure Intellij is configured to use **JDK 11**.
1. **Import the project as a Gradle project**: Follow the guide [_[se-edu/guides] IDEA: Importing a Gradle project_](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) to import the project into IDEA.
:exclamation: Note: Importing a Gradle project is slightly different from importing a normal Java project.
1. **Verify the setup**:
@@ -45,11 +45,4 @@ If you plan to use Intellij IDEA (highly recommended):
1. **Learn the design**
- When you are ready to start coding, we recommend that you get some sense of the overall design by reading about [AddressBook’s architecture](DeveloperGuide.md#architecture).
-
-1. **Do the tutorials**
- These tutorials will help you get acquainted with the codebase.
-
- * [Tracing code](tutorials/TracingCode.md)
- * [Removing fields](tutorials/RemovingFields.md)
- * [Adding a new command](tutorials/AddRemark.md)
+ When you are ready to start coding, we recommend that you get some sense of the overall design by reading about [Productiv’s architecture](DeveloperGuide.md#architecture).
diff --git a/docs/Testing.md b/docs/Testing.md
index 8a99e82438a..763517331da 100644
--- a/docs/Testing.md
+++ b/docs/Testing.md
@@ -28,9 +28,9 @@ There are two ways to run tests.
This project has three types of tests:
-1. *Unit tests* targeting the lowest level methods/classes.
- e.g. `seedu.address.commons.StringUtilTest`
-1. *Integration tests* that are checking the integration of multiple code units (those code units are assumed to be working).
- e.g. `seedu.address.storage.StorageManagerTest`
-1. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
- e.g. `seedu.address.logic.LogicManagerTest`
+* *Unit tests* that target the lowest level methods/classes.
+ e.g. `seedu.address.commons.util.StringUtilTest`
+* *Integration tests* that check the integration of multiple code units (assumed to be working).
+ e.g. `seedu.address.storage.StoragePersonManagerTest`
+* *Hybrids of unit and integration tests* that check multiple code units as well as how they are connected together.
+ e.g. `seedu.address.logic.LogicPersonManagerTest`
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index b91c3bab04d..d7ba9fb218b 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -2,39 +2,73 @@
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.
-
* Table of Contents
{:toc}
--------------------------------------------------------------------------------------------------------------------
+## Introduction
+### Overview
+Productiv is a one-stop desktop app for product managers like yourself to organise your **deliverables**,
+**meetings** and **contacts** so that you can track your product's development easily.
+
+Productiv is optimized for use via Command Line Interface (CLI). Thus, if you like to type and/or type fast,
+Productiv has just become better for you. Nevertheless, Productiv still has the benefits of a Graphical User Interface (GUI).
+
+
+### Preview
+To get you familiarised, the following is Productiv's GUI.
+
+ 
+ Dashboard
+
+
+
+ **:information_source: GUI components:**
+
+ 1. **Navigation bar**: where you navigate to other modes.
+ 1. **Command box**: where you enter your commands.
+ 1. **Feedback box**: where you can see the feedback of your command.
+ If your command is successful, you can see a success message.
+ Otherwise, you can see an error message.
+ 1. **Left panel**: where you can view
+ * your product's overall completion percentage (in dashboard mode), or
+ * your list of deliverables, meetings, or contacts (in deliverable, meeting, or contact mode)
+ 1. **Right panel**: where you can view
+ * your product management schedule (in dashboard mode), or
+ * an expanded view of your selected deliverable, meeting, or contact (in deliverable, meeting, or contact mode)
+
+ Note: For modes other than the dashboard, you can see your file path at the bottom of your GUI.
+
-## Quick start
-1. Ensure you have Java `11` or above installed in your Computer.
+## Quick start
-1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases).
+1. Ensure you have Java `11` installed in your computer (it should be your default Java version).
-1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook.
+1. Download the latest `productiv.jar` from [here](https://github.com/AY2021S1-CS2103T-F11-2/tp/releases).
-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.
- 
+1. Copy the .jar file to an empty folder.
-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:
+1. From your terminal, navigate to the folder containing the .jar file and enter `java -jar productiv.jar` to start Productiv.
+Your dashboard should appear in a few seconds.
+Note that the app contains some sample data.
- * **`list`** : Lists all contacts.
+ 
+ Dashboard
+
+1. Type a command in the command box and press Enter to execute it.
+Here is a sequence of example commands you can try:
- * **`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.
+ 1. `switch dv`: Switches to deliverable mode.
- * **`delete`**`3` : Deletes the 3rd contact shown in the current list.
+ 1. `add t/Find profile page template by/11-12-2020 12:00 m/2.1.1`: Adds a deliverable with the
+ title `Find profile page template`, deadline `11-12-2020 12:00` and milestone `2.1.1`.
- * **`clear`** : Deletes all contacts.
+ 1. `delete 1`: Deletes the 1st deliverable shown.
- * **`exit`** : Exits the app.
+ 1. `exit`: Exits the app.
-1. Refer to the [Features](#features) below for details of each command.
+1. Refer to [Features](#features) below for details of each available command.
--------------------------------------------------------------------------------------------------------------------
@@ -44,135 +78,553 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo
**:information_source: Notes about the command format:**
-* Words in `UPPER_CASE` are the parameters to be supplied by the user.
- e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`.
-
-* Items in square brackets are optional.
- e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`.
+* Words in upper case are the parameters to be supplied by you for their respective fields.
+ e.g. in `add n/NAME`, `NAME` is a parameter for the name field `n`, which can be used as `add n/Jason`.
+:bulb: **Tip:** If you are not sure what specific parameter to supply for any of the required fields, supply an estimate or random value as place holder.
-* Items with `…` after them can be used multiple times including zero times.
- e.g. `[t/TAG]…` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc.
+* Field-parameter pairs in square brackets are optional.
+ e.g `n/NAME [p/PHONE]` can be used as `n/Jason p/98890112` or as `n/Jason`.
-* Parameters can be in any order.
- e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable.
+* Field-parameter pairs can be in any order.
+ e.g. if the command specifies `n/NAME e/EMAIL`, `e/EMAIL n/NAME` is also acceptable.
+
+* If multiple and/or repeat parameters are provided for the same field, only the last parameter will be accepted.
+ e.g. if you input the command `add r/dev n/NAME r/stk e/EMAIL r/stk`, it will be accepted as `add n/NAME r/stk e/EMAIL`.
+* For single-word commands without fields, any word(s) following it will be ignored.
+ e.g. if you input the command `list everything please`, it will be accepted as `list`.
-### Viewing help : `help`
-Shows a message explaning how to access the help page.
+### General
+
+#### Switching modes: `switch`
+
+Switches to dashboard, deliverable, meeting or contact mode.
+
+Format: `switch MODE`
+* `MODE` can be `db` (dashboard), `dv` (deliverable), `m` (meeting) or `c` (contact).
+* `switch` `dv`, `m` or `c` will display information related to your deliverables, meetings and contacts respectively,
+e.g. `switch c` will display your contacts.
+* `switch db` will display your project's completion status and your own schedule.
+* How the commands will be executed depend on which mode you are currently in, e.g. `delete 1` in meeting mode deletes the 1st meeting shown.
-
+Examples:
+* `switch db` switches to dashboard mode.
+* `switch m` switches to meeting mode.
+
+#### Viewing help: `help`
+
+Shows a message directing you to this User Guide.
+
Format: `help`
+#### Exiting Productiv: `exit`
+
+Exits the program.
+
+Format: `exit`
+
+#### Saving the data
+
+Productiv automatically saves any changes that you made, to your computer's hard disk.
+Hence, you can focus on managing your product without fearing any unsaved changes.
+
-### Adding a person: `add`
+### Dashboard
-Adds a person to the address book.
+The [dashboard](#preview) gives you an overview of information related to your product.
+The dashboard is the default landing page of Productiv.
+Whenever you start up Productiv, you will be brought to dashboard mode.
-Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
+The Overall Completion Percentage (OCP) will be displayed in the left panel. The OCP gives you a quick overview of the progress of your product’s development.
+
+Your schedule will be displayed in the right panel. The schedule contains all your deliverables and meetings, chronologically sorted.
+
+
+
+
+**:information_source: Note:** There are no commands specific to the Dashboard.
+You can only use commands found under [General](#general).
+
+
+
+### Deliverable
+
+ 
+ Initial display of a deliverable list
+
+
+
+**:information_source: Note:** You must be in the deliverable mode to execute the following commands.
+Refer to [switch](#switching-modes-switch) for more information.
-
:bulb: **Tip:**
-A person can have any number of tags (including 0)
+
+
+#### Adding a deliverable: `add`
+
+Adds a deliverable to your deliverable list.
+
+Format: `add t/TITLE by/DEADLINE m/MILESTONE [c/CONTACTS] [d/DESCRIPTION]`
+* `TITLE` is the main heading of the deliverable.
+* `DEADLINE` is the due date time of the deliverable in dd-MM-yyyy HH:mm format.
+* `DEADLINE` can be in the past but must not be earlier than the year 2019.
+* `MILESTONE` is the milestone tagged to the deliverable.
+* `MILESTONE` is a non-negative integer, or a period-separated string of non-negative integers, e.g. `2`, `14.2.1`.
+* `CONTACTS` represents the contact(s) involved in seeing through the deliverable.
+* `CONTACTS` is a name, or a comma-separated string of names, e.g. `conan`, `nancy, drew, paul`.
+* `DESCRIPTION` contains additional information about the deliverable, e.g. sub-requirements.
+
+
+
+**:information_source: Notes:**
+
+* As `CONTACTS` is not related to your contact list, you can include those not present in it.
+
+* You cannot add a deliverable with the same `TITLE` and `DEADLINE` as an existing deliverable.
+
+* All newly-added deliverables will be assigned the `on-going` tag regardless of their deadline. You will
+need to manually mark past deliverables as `completed`. Refer to
+[Marking a deliverable as completed](#marking-a-deliverable-as-completed-done) below for more details on
+the `done` command.
+
+
+
+Examples:
+* `add t/Login screen by/10-10-2020 18:00 m/1.1 c/Jordan Woods, Betsy Crowe d/Include email and password fields`
+adds a deliverable with the title `Login screen`, deadline `10-10-2020 18:00`,
+milestone `1.1`, contacts `Jordan Woods, Betsy Crowe` and description `Include email and password fields`.
+* `add t/Find profile page template by/08-12-2020 12:00 m/2.1.1`
+adds a deliverable with the title `Find profile page template`, deadline `08-12-2020 12:00` and milestone `2.1.1`.
+
+#### Editing a deliverable: `edit`
+
+Edits an existing deliverable in your displayed deliverable list.
+
+Format: `edit INDEX [t/TITLE] [by/DEADLINE] [m/MILESTONE] [c/CONTACTS] [d/DESCRIPTION] `
+* `INDEX` is the index number of the deliverable in your displayed deliverable list.
+* `INDEX` must be a positive integer.
+* At least one of the fields of the deliverable must be changed.
+* The existing values of the specified deliverable will be updated to the input values.
+* You can clear an optional field by inputting an empty parameter, e.g. `edit 1 d/` will empty the description of the 1st deliverable.
+
+
+
+**:information_source: Note:** You cannot edit a deliverable to have the same `TITLE` and `DEADLINE` as an existing deliverable.
+
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`
+* `edit 1 d/Must include username, email and password fields by/15-12-2020 12:00`
+edits the description of the 1st deliverable to be `Must include username, email and password fields`
+and its deadline to be `15-12-2020 12:00`.
+* `edit 2 c/` clears the optional contacts field of the 2nd deliverable.
+
+#### Marking a deliverable as completed: `done`
+
+Marks the specified deliverable from your displayed deliverable list as completed.
+
+Format: `done INDEX`
+* `INDEX` is the index number of the deliverable in your displayed deliverable list.
+* `INDEX` must be a positive integer.
+
+Example:
+* `done 1` marks the 1st deliverable in your displayed deliverable list as completed.
+
+#### Marking a deliverable as on-going: `undone`
+
+Marks the specified deliverable from your displayed deliverable list as on-going.
+
+Format: `undone INDEX`
+* `INDEX` is the index number of the deliverable in your displayed deliverable list.
+* `INDEX` must be a positive integer.
+
+Example:
+* `undone 1` marks the 1st deliverable in your displayed deliverable list as on-going.
+
+#### Viewing a deliverable: `view`
+
+Displays more details of the specified deliverable from your displayed deliverable list.
-### Listing all persons : `list`
+Format: `view INDEX`
+* `INDEX` is the index number of the deliverable in your displayed deliverable list.
+* `INDEX` must be a positive integer.
-Shows a list of all persons in the address book.
+Example:
+* `view 2` views the 2nd deliverable in your displayed deliverable list.
+
+ 
+ Viewing a deliverable
+
+#### Finding deliverables: `find`
+
+Finds the deliverables whose titles or descriptions contain any of the given keywords.
+
+Format: `find KEYWORDS`
+* `KEYWORDS` contains one or more keywords used to match deliverables.
+* Searches only consider title and description.
+* Searches are case-insensitive, e.g. `homepage` will match `Homepage`.
+* Order of keywords does not matter, e.g. `Homepage Navigation` will match `Navigation Homepage`.
+* Searches only account for full words, e.g. `Deploy` will not match `Deployment` but `Stand-up` will match `Up button` and `Laptop stand`.
+* Searches return deliverables matching at least one keyword, e.g. `Homepage Navigation` will return `Complete Homepage` and `Increase size of Navigation Bar`.
+
+Examples:
+* `find mock-up urgent` returns a deliverable with title `Finish mock-ups` and another with description `This is urgent and important!`.
+* `find plan` returns a deliverable with title `Finalise design and plan` and another with description `Reminder to plan time wisely.`.
+
+#### Listing all deliverables: `list`
+
+Lists out all deliverables in your deliverable list, if any.
Format: `list`
-### Editing a person : `edit`
+
:bulb:
+
+**Tip:** Use this command when you want to list all your deliverables back after using the `find` command.
+Refer to [Finding deliverables](#finding-deliverables-find) above for details of the `find` command.
+
+
+#### Deleting a deliverable: `delete`
+
+Deletes the specified deliverable from your deliverable list.
+
+Format: `delete INDEX`
+* `INDEX` is the index number of the deliverable in your displayed deliverable list.
+* `INDEX` must be a positive integer.
+
+Example:
+* `delete 2` deletes the 2nd deliverable in your deliverable list.
+
+#### Clearing all deliverables: `clear`
+
+Clears all deliverables from your deliverable list, if any.
+
+Format: `clear`
+
+### Meeting
+
+ 
+ Initial display of a meeting list
+
+
+
+**:information_source: Note:** You must be in the meeting mode to execute the following commands.
+Refer to [switch](#switching-modes-switch) for more information.
+
+
+
+#### Adding a meeting: `add`
+
+Adds a meeting to your meeting list.
+
+Format: `add t/TITLE from/FROM to/TO [c/CONTACTS] [l/LOCATION] [d/DESCRIPTION]`
+* `TITLE` is the main heading of the meeting.
+* `FROM` is the start date and time of the meeting in dd-MM-yyyy HH:mm format.
+* `FROM` can accept dates in the past but must not be earlier than the year 2019.
+* `TO` is the end time of the meeting in HH:mm format.
+* `CONTACTS` represents the contact(s) involved in the meeting.
+* `CONTACTS` is a name, or a comma-separated string of names, e.g. `conan`, `nancy, drew, paul`.
+* `LOCATION` is the location of the meeting.
+* `DESCRIPTION` contains additional details about the meeting, e.g. agenda.
+
+
+
+**:information_source: Notes:**
-Edits an existing person in the address book.
+* Different meetings may have overlapping timings as you may wish to send a representative for your clashing meetings.
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
+* As `CONTACTS` is not related to your contact list, you can include those not present in it.
-* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …
-* At least one of the optional fields must be provided.
-* Existing values will be updated to the input values.
-* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative.
-* You can remove all the person’s tags by typing `t/` without
- specifying any tags after it.
+* You cannot add a meeting with the same `TITLE`, `FROM` and `TO` as an existing meeting.
+
+
+
+Example:
+* `add t/Discuss app requirements from/11-12-2020 09:00 to/10:00 c/Jordan Woods, Betsy Crowe d/Refine with business associates`
+adds a meeting with the title `Discuss app requirements`, start date and time `11-12-2020 09:00`,
+end time `10:00`, contacts `Jordan Woods, Betsy Crowe` and description `Refine with business associates`.
+* `add t/User research review from/15-12-2020 13:00 to/15:00 l/Meeting room A`
+adds a meeting with the title `User research review`, start date and time `15-12-2020 13:00`,
+end time `15:00` and location `Meeting room A`.
+
+#### Editing a meeting: `edit`
+
+Edits an existing meeting in your displayed meeting list.
+
+Format: `edit INDEX [t/TITLE] [from/FROM] [to/TO] [c/CONTACTS] [l/LOCATION] [d/DESCRIPTION]`
+* `INDEX` is the index number of the meeting in your displayed meeting list.
+* `INDEX` must be a positive integer.
+* At least one of the fields of the meeting must be changed.
+* The existing values of the specified meeting will be updated to the input values.
+* You can clear an optional field by inputting an empty parameter, e.g. `edit 1 d/` will empty the description of the 1st meeting.
+
+
+
+**:information_source: Note:** You cannot edit a meeting to have the same `TITLE`, `FROM` and `TO` as an existing meeting.
+
+
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 2 t/Discuss final release features d/Finalise dashboard functions`
+edits the title of the 2nd meeting to be `Discuss final release features`
+and its description to be `Finalise dashboard functions`.
+* `edit 4 c/` clears the optional contact field of the 4th meeting.
+
+#### Viewing a meeting: `view`
-### Locating persons by name: `find`
+Displays more details of the specified meeting from your displayed meeting list.
-Finds persons whose names contain any of the given keywords.
+Format: `view INDEX`
+* `INDEX` is the index number of the meeting in your displayed meeting list.
+* `INDEX` must be a positive integer.
-Format: `find KEYWORD [MORE_KEYWORDS]`
+Example:
+* `view 2` views the 2nd meeting in your meeting list.
-* 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`
+ 
+ Viewing a meeting
+
+#### Finding meetings: `find`
+
+Finds the meetings whose titles or descriptions contain any of the given keywords.
+
+Format: `find KEYWORDS`
+* `KEYWORDS` contains one or more keywords used to match meetings.
+* Searches only consider title and description.
+* Searches are case-insensitive, e.g. `version` will match `Version`.
+* Order of keywords does not matter, e.g. `v1.2 mid` will match `mid v1.2`.
+* Searches only account for full words, e.g. `Meeting` will not match `Meetings`.
+* Searches return meetings matching at least one keyword, e.g. `Complete game` will return `Complete features` and `Final game`.
Examples:
-* `find John` returns `john` and `John Doe`
-* `find alex david` returns `Alex Yeoh`, `David Li`
- 
+* `find Survey` returns a meeting with title `Survey potential customers` and another with description `Don't forget to present survey results.`.
+* `find consult goals` returns a meeting with title `Consult about marketing goals` and another with description `Goals must be achieved!`.
+
+#### Listing all meetings: `list`
+
+Lists out all meetings in your meeting list, if any.
+
+Format: `list`
+
+
:bulb:
+
+**Tip:** Use this command when you want to list all your meetings back after using the `find` command.
+Refer to [Finding meetings](#finding-meetings-find) above for details of the `find` command.
+
-### Deleting a person : `delete`
+#### Deleting a meeting: `delete`
-Deletes the specified person from the address book.
+Deletes the specified meeting from your displayed meeting list.
Format: `delete INDEX`
+* `INDEX` is the index number of the meeting in your displayed meeting list.
+* `INDEX` must be a positive integer.
+
+Example:
+* `delete 3` deletes the 3rd meeting in your displayed meeting list.
+
+#### Clearing all meetings: `clear`
+
+Clears all meetings from your meeting list, if any.
+
+Format: `clear`
+
+### Contact
+
+ 
+ Initial display of a contact list
+
+
+
+**:information_source: Note:** You must be in the contact mode to execute the following commands.
+Refer to [switch](#switching-modes-switch) for more information.
+
+
+
+#### Adding a contact: `add`
-* 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, …
+Adds a developer or stakeholder to your contact list.
+
+Format: `add n/NAME r/ROLE e/EMAIL [p/PHONE] [d/DESCRIPTION]`
+* `NAME` is the name of the contact.
+* `NAME` should only take alphabetic characters and (optionally) spaces.
+* `ROLE` is the type of contact, either `dev` (developer) or `stk` (stakeholder).
+* `EMAIL` is the email address of the contact.
+* `PHONE` is the phone number of the contact.
+* `PHONE` should only contain numbers, and must be at least 3-digits long.
+* `DESCRIPTION` contains additional information about the contact, such as their job position.
+
+
+
+**:information_source: Note:** You cannot add a contact with the same `NAME` and `EMAIL` as an existing contact.
+
+
+
+
:bulb:
+
+**Tip:** Leave out the + sign for `PHONE`s with country codes.
+
+
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.
+* `add n/Jordan Woods r/dev e/jordanwoods@glutter.com p/81234567`
+adds a developer with the name `Jordan Woods`, email `jordanwoods@glutter.com` and phone number `81234567`.
+* `add n/Betsy Crowe r/stk e/betsybet872@pmail.com`
+adds a stakeholder with the name `Betsy Crowe` and email `betsybet872@pmail.com`.
-### Clearing all entries : `clear`
+#### Editing a contact: `edit`
-Clears all entries from the address book.
+Edits an existing contact in your displayed contact list.
-Format: `clear`
+Format: `edit INDEX [n/NAME] [r/ROLE] [e/EMAIL] [p/PHONE] [d/DESCRIPTION]`
+* `INDEX` is the index number of the contact in your displayed contact list.
+* `INDEX` must be a positive integer.
+* At least one of the fields of the contact must be changed.
+* The existing values of the specified contact will be updated to the input values.
+* You can clear an optional field by inputting an empty parameter, e.g. `edit 1 d/` will empty the description of the 1st contact.
-### Exiting the program : `exit`
+
-Exits the program.
+**:information_source: Note:** You cannot edit a contact to have the same `NAME` and `EMAIL` as an existing contact.
+
+
-Format: `exit`
+Examples:
+* `edit 1 e/jeremysand@glutter.com p/81234567`
+edits the email and phone number of the 1st contact to be `jeremysand@glutter.com` and `81234567` respectively.
+* `edit 2 p/`
+clears the optional phone field of the 2nd contact.
-### Saving the data
+#### Viewing a contact: `view`
-AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
+Displays more details of the specified contact from your displayed contact list.
-### Archiving data files `[coming in v2.0]`
+Format: `view INDEX`
+* `INDEX` is the index number of the contact in your displayed contact list.
+* `INDEX` must be a positive integer.
-_{explain the feature here}_
+Example:
+* `view 2` views the 2nd contact in your contact list.
+
+ 
+ Viewing a contact
+
+#### Finding contacts: `find`
+
+Finds the contacts whose names or descriptions contain any of the given keywords.
+
+Format: `find KEYWORDS`
+* `KEYWORDS` contains one or more keywords used to match contacts.
+* Searches only consider name and description.
+* Searches are case-insensitive, e.g. `hans` will match `Hans`.
+* Order of keywords does not matter, e.g. `Hans Bo` will match `Bo Hans`.
+* Searches only account for full words, e.g. `Han` will not match `Hans`.
+* Searches return contacts matching at least one keyword, e.g. `Hans Bo` will return `Hans Seed` and `Bo Yarns`.
+
+Examples:
+* `find alex yeoh` returns a contact with name `Alex Yeoh` and another with description `Business analyst. Alex works with him.`.
+* `find Johnson` returns a contact with name `Amber Johnson` and another with description `Works at Johnson & Johnson`.
+
+#### Listing all contacts: `list`
+
+Lists out all contacts from your contact list, if any.
+
+Format: `list`
+
+
:bulb:
+
+**Tip:** Use this command when you want to list all your contacts back after using the `find` command.
+Refer to [Finding contacts](#finding-contacts-find) above for details of the `find` command.
+
+
+#### Deleting a contact: `delete`
+
+Deletes the specified contact from your displayed contact list.
+
+Format: `delete INDEX`
+* `INDEX` is the index number of the contact in your displayed contact list.
+* `INDEX` must be a positive integer.
+
+Example:
+* `delete 3` deletes the 3rd contact in the displayed contact list.
+
+#### Clearing all contacts: `clear`
+
+Clears all contacts from your contact list, if any.
+
+Format: `clear`
--------------------------------------------------------------------------------------------------------------------
## FAQ
-**Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder.
+**Q**: How do I start using Productiv?
+**A**: You can refer to our [Quick Start Guide](#quick-start).
---------------------------------------------------------------------------------------------------------------------
+**Q**: Which operating systems can I run Productiv on?
+**A**: Currently, Productiv is supported on both Windows and Mac. Just ensure
+that you have Java `11` installed on your computer and it is your default Java version.
+
+---------------------------------------------------------------------------------------------------------------------
## Command summary
-Action | Format, Examples
---------|------------------
-**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…` e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague`
-**Clear** | `clear`
-**Delete** | `delete INDEX` e.g., `delete 3`
-**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…` e.g.,`edit 2 n/James Lee e/jameslee@example.com`
-**Find** | `find KEYWORD [MORE_KEYWORDS]` e.g., `find James Jake`
-**List** | `list`
-**Help** | `help`
+### General
+
+Action | Format, Examples
+-------------- |--------------------------------------------------------------------------------------------------
+**Switch** | `switch MODE` e.g. `switch dv`
+**Help** | `help`
+**Exit** | `exit`
+
+### Deliverable
+
+Action | Format, Examples
+---------------|------------------------
+Add | `add t/TITLE by/DEADLINE m/MILESTONE [c/CONTACTS] [d/DESCRIPTION]` e.g. `add t/Login screen by/10-10-2020 18:00 m/1.1 c/Jordan Woods, Betsy Crowe d/Include email and password fields`
+Edit | `edit INDEX [t/TITLE] [by/DEADLINE] [m/MILESTONE] [c/CONTACTS] [d/DESCRIPTION]` e.g. `edit 1 by/14-12-2020 12:00 d/Must include username, email and password fields`
+Mark as completed | `done INDEX` e.g. `done 3`
+Mark as on-going | `undone INDEX` e.g. `undone 1`
+View | `view INDEX` e.g. `view 2`
+Find | `find KEYWORDS` e.g. `find Homepage urgent`
+List | `list`
+Delete | `delete INDEX` e.g. `delete 3`
+Clear | `clear`
+
+### Meeting
+
+Action | Format, Examples
+---------------|------------------------
+Add | `add t/TITLE from/FROM to/TO [c/CONTACTS] [l/LOCATION] [d/DESCRIPTION]` e.g. `add t/Discuss app requirements from/11-12-2020 09:00 to/10:00 c/Jordan Woods, Betsy Crowe l/Meeting Room A d/Refine with business associates`
+Edit | `edit INDEX [t/TITLE] [from/FROM] [to/TO] [c/CONTACTS] [l/LOCATION] [d/DESCRIPTION]` e.g. `edit 2 t/Discuss final release features d/Finalise dashboard functions`
+View | `view INDEX` e.g. `view 2`
+Find | `find KEYWORDS` e.g. `find discuss user guide John`
+List | `list`
+Delete | `delete INDEX` e.g. `delete 3`
+Clear | `clear`
+
+### Contact
+
+Action | Format, Examples
+---------------|------------------------
+Add | `add n/NAME r/ROLE e/EMAIL [p/PHONE] [d/DESCRIPTION]` e.g. `add n/Johnny r/stk e/johnny@example.com p/12345678 d/Business Analyst`
+Edit | `edit INDEX [n/NAME] [r/ROLE] [e/EMAIL] [p/PHONE] [d/DESCRIPTION]` e.g. `edit 1 n/John r/dev e/john@email.com`
+View | `view INDEX` e.g. `view 2`
+Find | `find KEYWORDS` e.g. `find John Kite`
+List | `list`
+Delete | `delete INDEX` e.g. `delete 3`
+Clear | `clear`
+
+---------------------------------------------------------------------------------------------------------------------
+
+## Glossary
+
+\# | Term | Description
+---|--------------------------------|------------------------------------------------------------------------------------------------|
+1 | Command Line Interface (CLI) | A text-based user interface (UI) used to view and manage computer files. |
+2 | Graphical User Interface (GUI) | A system of interactive visual components for computer software. |
+3 | Deliverable | An item to be completed as part of the product development process. |
+4 | Milestone | A stage in the software development process associated with a particular group of deliverables.|
+5 | Mode | The state of the application that affects how each command will be executed. The app can be in dashboard, deliverable, meeting or contact mode. |
+6 | Stakeholder | An external party involved with the product. |
diff --git a/docs/_config.yml b/docs/_config.yml
index 6bd245d8f4e..bc0f65b91f9 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,4 +1,4 @@
-title: "AB-3"
+title: "Productiv"
theme: minima
header_pages:
@@ -8,7 +8,7 @@ header_pages:
markdown: kramdown
-repository: "se-edu/addressbook-level3"
+repository: "AY2021S1-CS2103T-F11-2/tp"
github_icon: "images/github-icon.png"
plugins:
diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss
index 06e47f23762..d8ef0280244 100644
--- a/docs/_sass/minima/_base.scss
+++ b/docs/_sass/minima/_base.scss
@@ -79,6 +79,10 @@ figure > img {
figcaption {
font-size: $small-font-size;
+ color: #2e7d32;
+ display: block;
+ text-align: center;
+ font-style: italic;
}
diff --git a/docs/_sass/minima/initialize.scss b/docs/_sass/minima/initialize.scss
index 30288811151..8c3e194354f 100644
--- a/docs/_sass/minima/initialize.scss
+++ b/docs/_sass/minima/initialize.scss
@@ -14,7 +14,7 @@ $spacing-unit: 30px !default;
$table-text-align: left !default;
// Width of the content area
-$content-width: 800px !default;
+$content-width: 1200px !default;
$on-palm: 600px !default;
$on-laptop: 800px !default;
diff --git a/docs/assets/css/style.scss b/docs/assets/css/style.scss
index b5ec6976efa..5d3bf0ea2bc 100644
--- a/docs/assets/css/style.scss
+++ b/docs/assets/css/style.scss
@@ -1,7 +1,6 @@
---
# Only the main Sass file needs front matter (the dashes are enough)
---
-
@import
"minima/skins/{{ site.minima.skin | default: 'classic' }}",
"minima/initialize";
diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml
index ef81d18c337..ed710ee7fb0 100644
--- a/docs/diagrams/ArchitectureSequenceDiagram.puml
+++ b/docs/diagrams/ArchitectureSequenceDiagram.puml
@@ -3,9 +3,9 @@
Actor User as user USER_COLOR
Participant ":UI" as ui UI_COLOR
-Participant ":Logic" as logic LOGIC_COLOR
-Participant ":Model" as model MODEL_COLOR
-Participant ":Storage" as storage STORAGE_COLOR
+Participant ":LogicPerson" as logic LOGIC_COLOR
+Participant ":ModelPerson" as modelPerson MODEL_COLOR
+Participant ":StoragePerson" as storagePerson STORAGE_COLOR
user -[USER_COLOR]> ui : "delete 1"
activate ui UI_COLOR
@@ -13,22 +13,22 @@ activate ui UI_COLOR
ui -[UI_COLOR]> logic : execute("delete 1")
activate logic LOGIC_COLOR
-logic -[LOGIC_COLOR]> model : deletePerson(p)
-activate model MODEL_COLOR
+logic -[LOGIC_COLOR]> modelPerson : deletePerson(p)
+activate modelPerson MODEL_COLOR
-model -[MODEL_COLOR]-> logic
-deactivate model
+modelPerson -[MODEL_COLOR]-> logic
+deactivate modelPerson
-logic -[LOGIC_COLOR]> storage : saveAddressBook(addressBook)
-activate storage STORAGE_COLOR
+logic -[LOGIC_COLOR]> storagePerson : saveAddressBook(addressBook)
+activate storagePerson STORAGE_COLOR
-storage -[STORAGE_COLOR]> storage : Save to file
-activate storage STORAGE_COLOR_T1
-storage --[STORAGE_COLOR]> storage
-deactivate storage
+storagePerson -[STORAGE_COLOR]> storagePerson : Save to file
+activate storagePerson STORAGE_COLOR_T1
+storagePerson --[STORAGE_COLOR]> storagePerson
+deactivate storagePerson
-storage --[STORAGE_COLOR]> logic
-deactivate storage
+storagePerson --[STORAGE_COLOR]> logic
+deactivate storagePerson
logic --[LOGIC_COLOR]> ui
deactivate logic
diff --git a/docs/diagrams/ArchitectureSequenceDiagramSwitch.puml b/docs/diagrams/ArchitectureSequenceDiagramSwitch.puml
new file mode 100644
index 00000000000..a43c3132eb7
--- /dev/null
+++ b/docs/diagrams/ArchitectureSequenceDiagramSwitch.puml
@@ -0,0 +1,19 @@
+@startuml
+!include style.puml
+
+Actor User as user USER_COLOR
+Participant ":UI" as ui UI_COLOR
+Participant ":LogicMode" as logic LOGIC_COLOR
+
+user -[USER_COLOR]> ui : "switch m"
+activate ui UI_COLOR
+
+ui -[UI_COLOR]> logic : execute("switch m")
+activate logic LOGIC_COLOR
+
+logic --[LOGIC_COLOR]> ui
+deactivate logic
+
+ui--[UI_COLOR]> user
+deactivate ui
+@enduml
diff --git a/docs/diagrams/ArchitectureSequenceDiagramWithDb.puml b/docs/diagrams/ArchitectureSequenceDiagramWithDb.puml
new file mode 100644
index 00000000000..b4f68d5983b
--- /dev/null
+++ b/docs/diagrams/ArchitectureSequenceDiagramWithDb.puml
@@ -0,0 +1,66 @@
+@startuml
+!include style.puml
+
+Actor User as user USER_COLOR
+box "UI" #LightGreen
+Participant ":UI" as ui UI_COLOR_T2
+Participant ":Calendar" as calendar UI_COLOR_T3
+Participant ":ProjectCompletionStatusPanel" as ocp UI_COLOR_T4
+end box
+
+box "Logic" #LightBlue
+Participant ":LogicXYZ" as logic LOGIC_COLOR
+end box
+
+box "Model" #Pink
+Participant ":ModelXYZ" as modelPerson MODEL_COLOR
+end box
+
+box "Storage" #Orange
+Participant ":StorageXYZ" as storageMeeting STORAGE_COLOR
+end box
+
+user -[USER_COLOR]> ui : "delete 1"
+activate ui UI_COLOR_T2
+
+ui -[UI_COLOR_T2]> logic : execute("delete 1")
+activate logic LOGIC_COLOR
+
+logic -[LOGIC_COLOR]> modelPerson : deleteXYZ(xyz)
+activate modelPerson MODEL_COLOR
+note left of modelPerson: XYZ = Deliverable, Meeting, or Person \n Similarly, xyz = deliverable, meeting, or person
+
+modelPerson -[MODEL_COLOR]-> logic
+deactivate modelPerson
+
+logic -[LOGIC_COLOR]> storageMeeting : saveXYZBook(xyzBook)
+activate storageMeeting STORAGE_COLOR
+
+storageMeeting -[STORAGE_COLOR]> storageMeeting : Save to file
+activate storageMeeting STORAGE_COLOR_T1
+storageMeeting --[STORAGE_COLOR]> storageMeeting
+deactivate storageMeeting
+
+storageMeeting --[STORAGE_COLOR]> logic
+deactivate storageMeeting
+
+logic --[LOGIC_COLOR]> ui
+deactivate logic
+
+opt in deliverable or meeting mode
+ ui--[UI_COLOR_T2]> calendar : updateCalendarList()
+ activate calendar UI_COLOR_T3
+
+ calendar --[UI_COLOR_T3]> ui
+ deactivate calendar
+
+ ui--[UI_COLOR_T2]> ocp : updateOcp()
+ activate ocp UI_COLOR_T4
+
+ ocp --[UI_COLOR_T4]> ui
+ deactivate ocp
+
+end
+ui--[UI_COLOR_T2]> user
+deactivate ui
+@enduml
diff --git a/docs/diagrams/AutosortSequenceDiagram.puml b/docs/diagrams/AutosortSequenceDiagram.puml
new file mode 100644
index 00000000000..8341c30e08b
--- /dev/null
+++ b/docs/diagrams/AutosortSequenceDiagram.puml
@@ -0,0 +1,34 @@
+@startuml
+!include style.puml
+
+box Model MODEL_COLOR_T1
+participant ":UniqueMeetingList" as UniqueMeetingList MODEL_COLOR
+participant ":ObservableList" as ObservableList MODEL_COLOR
+
+end box
+
+participant "<>\n:Collections" as Collections RANDOM_COLOR
+
+[-> UniqueMeetingList : add(meeting)
+activate UniqueMeetingList
+
+UniqueMeetingList -> ObservableList : add(meeting)
+activate ObservableList
+
+ObservableList --> UniqueMeetingList :
+deactivate
+
+UniqueMeetingList -> UniqueMeetingList : sortList()
+activate UniqueMeetingList #DarkSalmon
+
+UniqueMeetingList -> Collections : sort(list, timeEventComparator)
+activate Collections
+
+Collections --> UniqueMeetingList :
+deactivate Collections
+
+return
+
+[<--UniqueMeetingList
+deactivate UniqueMeetingList
+@enduml
diff --git a/docs/diagrams/CalendarSequenceDiagram.puml b/docs/diagrams/CalendarSequenceDiagram.puml
new file mode 100644
index 00000000000..1e16d227b54
--- /dev/null
+++ b/docs/diagrams/CalendarSequenceDiagram.puml
@@ -0,0 +1,43 @@
+@startuml
+!include style.puml
+
+box UI UI_COLOR_T2
+participant ":MainWindow" as MainWindow UI_COLOR
+participant ":Calendar" as Calendar UI_COLOR
+participant "calendarList:ObservableList" as TimeEvents UI_COLOR
+end box
+
+participant "<>\n:Collections" as Collections RANDOM_COLOR
+
+[-> MainWindow : executeCommand("delete 1")
+activate MainWindow
+
+MainWindow -> Calendar : updateCalendarList()
+activate Calendar
+
+Calendar -> TimeEvents : clear()
+activate TimeEvents
+
+return
+
+Calendar -> TimeEvents : addAll(meetings)
+activate TimeEvents
+
+return
+
+Calendar -> TimeEvents : addAll(deliverables)
+activate TimeEvents
+
+return
+
+Calendar -> Collections : sort(calendarList, timeEventComparator)
+activate Collections
+
+return
+
+Calendar --> MainWindow :
+deactivate
+
+[<--MainWindow
+deactivate MainWindow
+@enduml
diff --git a/docs/diagrams/CommandSequenceDiagram.puml b/docs/diagrams/CommandSequenceDiagram.puml
new file mode 100644
index 00000000000..4417a686ab9
--- /dev/null
+++ b/docs/diagrams/CommandSequenceDiagram.puml
@@ -0,0 +1,135 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicDispatcherManager" as LogicDispatcherManager LOGIC_COLOR
+participant ":XYZLogicManager" as XYZLogicManager LOGIC_COLOR
+participant ":XYZBookParser" as XYZBookParser LOGIC_COLOR
+participant ":GeneralParser" as GeneralParser LOGIC_COLOR
+participant ":ABCCommandParser" as ABCCommandParser LOGIC_COLOR
+participant "abcCommand:ABCCommand" as ABCCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+
+end box
+
+box Model MODEL_COLOR_T1
+participant ":XYZModel" as XYZModel MODEL_COLOR
+end box
+
+[-> LogicDispatcherManager : execute(command, XYZ)
+activate LogicDispatcherManager
+note left
+XYZ = Deliverable,
+Meeting or Person
+end note
+
+alt LOGIC_COLOR_T1 commands that affect model
+LogicDispatcherManager -> XYZLogicManager : execute(command)
+activate XYZLogicManager
+
+XYZLogicManager -> XYZBookParser : parseCommand(command)
+activate XYZBookParser
+
+create ABCCommandParser
+XYZBookParser -> ABCCommandParser
+activate ABCCommandParser
+
+ABCCommandParser --> XYZBookParser
+deactivate ABCCommandParser
+
+XYZBookParser -> ABCCommandParser : parse(command)
+activate ABCCommandParser
+
+create ABCCommand
+ABCCommandParser -> ABCCommand
+activate ABCCommand
+
+
+ABCCommand --> ABCCommandParser : abcCommand
+deactivate ABCCommand
+note right
+ABC = Add, Find, Delete, etc
+Similarly, abc = add, find, delete, etc
+end note
+
+ABCCommandParser --> XYZBookParser : abcCommand
+deactivate ABCCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+ABCCommandParser -[hidden]-> XYZBookParser
+destroy ABCCommandParser
+
+XYZBookParser --> XYZLogicManager : abcCommand
+deactivate XYZBookParser
+
+XYZLogicManager -> ABCCommand : execute()
+activate ABCCommand
+
+ABCCommand -> XYZModel
+activate XYZModel
+
+XYZModel --> ABCCommand
+deactivate XYZModel
+
+create CommandResult
+ABCCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> ABCCommand
+deactivate CommandResult
+
+ABCCommand --> XYZLogicManager : result
+deactivate ABCCommand
+
+LogicDispatcherManager<--XYZLogicManager
+deactivate XYZLogicManager
+
+else #yellow commands that do not affect model
+
+LogicDispatcherManager -> GeneralParser : parseCommand(command)
+activate GeneralParser
+
+create ABCCommandParser
+GeneralParser -> ABCCommandParser
+activate ABCCommandParser
+
+ABCCommandParser --> GeneralParser
+deactivate ABCCommandParser
+
+GeneralParser -> ABCCommandParser : parse(command)
+activate ABCCommandParser
+
+create ABCCommand
+ABCCommandParser -> ABCCommand
+activate ABCCommand
+
+
+ABCCommand --> ABCCommandParser : abcCommand
+deactivate ABCCommand
+
+ABCCommandParser --> GeneralParser : abcCommand
+deactivate ABCCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+ABCCommandParser -[hidden]-> GeneralParser
+destroy ABCCommandParser
+
+GeneralParser -->LogicDispatcherManager : abcCommand
+deactivate GeneralParser
+
+LogicDispatcherManager -> ABCCommand : execute()
+activate ABCCommand
+
+create CommandResult
+ABCCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> ABCCommand
+deactivate CommandResult
+
+ABCCommand --> LogicDispatcherManager : result
+deactivate ABCCommand
+
+end
+[<--LogicDispatcherManager
+deactivate LogicDispatcherManager
+
+@enduml
diff --git a/docs/diagrams/DateTimeClass.puml b/docs/diagrams/DateTimeClass.puml
new file mode 100644
index 00000000000..4561988b9a1
--- /dev/null
+++ b/docs/diagrams/DateTimeClass.puml
@@ -0,0 +1,31 @@
+@startuml
+!include style.puml
+
+Package Model <> {
+
+ Package Meeting <> {
+ Class Meeting MODEL_COLOR
+ Class From MODEL_COLOR_T3
+ Class To MODEL_COLOR_T3
+ }
+
+ Package Deliverable <>{
+ Class Deliverable MODEL_COLOR
+ Class Deadline MODEL_COLOR_T3
+ }
+
+ Package Util <> {
+ Class DateTime MODEL_COLOR_T2
+ Class pp #FFFFFF
+ Class Time MODEL_COLOR_T2
+ }
+}
+
+Meeting *-- From
+Meeting *-- To
+Deliverable *-- Deadline
+
+From -right-|> DateTime
+To --|> Time
+Deadline -down-|> DateTime
+@enduml
diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml
deleted file mode 100644
index 1dc2311b245..00000000000
--- a/docs/diagrams/DeleteSequenceDiagram.puml
+++ /dev/null
@@ -1,69 +0,0 @@
-@startuml
-!include style.puml
-
-box Logic LOGIC_COLOR_T1
-participant ":LogicManager" as LogicManager LOGIC_COLOR
-participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
-participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR
-participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR
-participant ":CommandResult" as CommandResult LOGIC_COLOR
-end box
-
-box Model MODEL_COLOR_T1
-participant ":Model" as Model MODEL_COLOR
-end box
-
-[-> LogicManager : execute("delete 1")
-activate LogicManager
-
-LogicManager -> AddressBookParser : parseCommand("delete 1")
-activate AddressBookParser
-
-create DeleteCommandParser
-AddressBookParser -> DeleteCommandParser
-activate DeleteCommandParser
-
-DeleteCommandParser --> AddressBookParser
-deactivate DeleteCommandParser
-
-AddressBookParser -> DeleteCommandParser : parse("1")
-activate DeleteCommandParser
-
-create DeleteCommand
-DeleteCommandParser -> DeleteCommand
-activate DeleteCommand
-
-DeleteCommand --> DeleteCommandParser : d
-deactivate DeleteCommand
-
-DeleteCommandParser --> AddressBookParser : d
-deactivate DeleteCommandParser
-'Hidden arrow to position the destroy marker below the end of the activation bar.
-DeleteCommandParser -[hidden]-> AddressBookParser
-destroy DeleteCommandParser
-
-AddressBookParser --> LogicManager : d
-deactivate AddressBookParser
-
-LogicManager -> DeleteCommand : execute()
-activate DeleteCommand
-
-DeleteCommand -> Model : deletePerson(1)
-activate Model
-
-Model --> DeleteCommand
-deactivate Model
-
-create CommandResult
-DeleteCommand -> CommandResult
-activate CommandResult
-
-CommandResult --> DeleteCommand
-deactivate CommandResult
-
-DeleteCommand --> LogicManager : result
-deactivate DeleteCommand
-
-[<--LogicManager
-deactivate LogicManager
-@enduml
diff --git a/docs/diagrams/DoneCommandSequenceDiagram.puml b/docs/diagrams/DoneCommandSequenceDiagram.puml
new file mode 100644
index 00000000000..7368fb2369c
--- /dev/null
+++ b/docs/diagrams/DoneCommandSequenceDiagram.puml
@@ -0,0 +1,61 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":DeliverableLogicManager" as DeliverableLogicManager LOGIC_COLOR_T2
+participant ":DeliverableBookParser" as DeliverableBookParser LOGIC_COLOR_T2
+participant ":MarkDoneCommandParser" as MarkDoneCommandParser LOGIC_COLOR_T2
+participant "d:MarkDoneCommand" as MarkDoneCommand LOGIC_COLOR_T2
+end box
+
+box Model MODEL_COLOR_T1
+participant ":ModelDeliverable" as ModelDeliverable MODEL_COLOR_T2
+end box
+
+[-> DeliverableLogicManager : execute("done 2")
+activate DeliverableLogicManager
+
+DeliverableLogicManager -> DeliverableBookParser : parseCommand("done 2")
+activate DeliverableBookParser
+
+create MarkDoneCommandParser
+DeliverableBookParser -> MarkDoneCommandParser
+activate MarkDoneCommandParser
+
+MarkDoneCommandParser --> DeliverableBookParser
+deactivate MarkDoneCommandParser
+
+DeliverableBookParser -> MarkDoneCommandParser : parse("2")
+activate MarkDoneCommandParser
+
+create MarkDoneCommand
+MarkDoneCommandParser -> MarkDoneCommand
+activate MarkDoneCommand
+
+MarkDoneCommand --> MarkDoneCommandParser : d
+deactivate MarkDoneCommand
+
+MarkDoneCommandParser --> DeliverableBookParser : d
+deactivate MarkDoneCommandParser
+destroy MarkDoneCommandParser
+
+DeliverableBookParser --> DeliverableLogicManager : d
+deactivate DeliverableBookParser
+
+DeliverableLogicManager -> MarkDoneCommand : execute()
+activate MarkDoneCommand
+
+MarkDoneCommand -> ModelDeliverable : updateDeliverableStatus()
+activate ModelDeliverable
+
+ModelDeliverable --> MarkDoneCommand
+deactivate ModelDeliverable
+
+MarkDoneCommand --> DeliverableLogicManager : result
+deactivate MarkDoneCommand
+destroy MarkDoneCommand
+
+[<-- DeliverableLogicManager : result
+deactivate DeliverableLogicManager
+
+@enduml
diff --git a/docs/diagrams/LogicClassDiagram.puml b/docs/diagrams/LogicClassDiagram.puml
index 016ef33e2e2..bd66030fffd 100644
--- a/docs/diagrams/LogicClassDiagram.puml
+++ b/docs/diagrams/LogicClassDiagram.puml
@@ -1,5 +1,8 @@
@startuml
!include style.puml
+
+'scale 1.5
+
skinparam arrowThickness 1.1
skinparam arrowColor LOGIC_COLOR_T4
skinparam classBackgroundColor LOGIC_COLOR
@@ -8,8 +11,8 @@ package Logic {
package Parser {
Interface Parser <>
-Class AddressBookParser
-Class XYZCommandParser
+Class XYZBookParser
+Class ABCCommandParser
Class CliSyntax
Class ParserUtil
Class ArgumentMultimap
@@ -18,45 +21,55 @@ Class Prefix
}
package Command {
-Class XYZCommand
+Class ABCCommand
Class CommandResult
Class "{abstract}\nCommand" as Command
}
-Interface Logic <>
-Class LogicManager
+Interface LogicXYZ <>
+Class LogicXYZManager
+
+Interface LogicDispatcher <>
+Class LogicDispatcherManager
+
+note top of ABCCommandParser: ABC = Add, Find,\nDelete, etc
+note top of XYZBookParser: XYZ = Deliverable, \nMeeting or Person
+
}
package Model{
-Class HiddenModel #FFFFFF
+class HiddenModel #FFFFFF
}
Class HiddenOutside #FFFFFF
-HiddenOutside ..> Logic
+HiddenOutside ..> LogicDispatcher
-LogicManager .up.|> Logic
-LogicManager -->"1" AddressBookParser
-AddressBookParser .left.> XYZCommandParser: creates >
+LogicDispatcherManager .up.|> LogicDispatcher
+XYZBookParser .left.> ABCCommandParser: creates >
-XYZCommandParser ..> XYZCommand : creates >
-XYZCommandParser ..|> Parser
-XYZCommandParser ..> ArgumentMultimap
-XYZCommandParser ..> ArgumentTokenizer
+ABCCommandParser ..> ABCCommand : creates >
+ABCCommandParser ..|> Parser
+ABCCommandParser ..> ArgumentMultimap
+ABCCommandParser ..> ArgumentTokenizer
ArgumentTokenizer .left.> ArgumentMultimap
-XYZCommandParser ..> CliSyntax
+ABCCommandParser ..> CliSyntax
CliSyntax ..> Prefix
-XYZCommandParser ..> ParserUtil
+ABCCommandParser ..> ParserUtil
ParserUtil .down.> Prefix
ArgumentTokenizer .down.> Prefix
-XYZCommand -up-|> Command
-LogicManager .left.> Command : executes >
+ABCCommand -up-|> Command
+LogicDispatcherManager .left.> Command : executes >
+LogicDispatcherManager -> "0..1 " Model
+LogicDispatcherManager ---> "0..1 " ABCCommandParser
-LogicManager --> Model
-Command .right.> Model
-note right of XYZCommand: XYZCommand = AddCommand, \nFindCommand, etc
+Command .> "0..1 " Model
-Logic ..> CommandResult
-LogicManager .down.> CommandResult
+LogicDispatcher ..> CommandResult
+LogicDispatcherManager .down.> CommandResult
Command .up.> CommandResult
CommandResult -[hidden]-> Parser
+
+LogicXYZ ..> LogicXYZManager
+LogicDispatcherManager -left-> LogicXYZManager
+LogicXYZManager -> XYZBookParser
@enduml
diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml
index e85a00d4107..ab4e728726d 100644
--- a/docs/diagrams/ModelClassDiagram.puml
+++ b/docs/diagrams/ModelClassDiagram.puml
@@ -5,52 +5,110 @@ skinparam arrowColor MODEL_COLOR
skinparam classBackgroundColor MODEL_COLOR
Package Model <>{
-Interface ReadOnlyAddressBook <>
-Interface Model <>
+Interface ReadOnlyXYZBook <>
+Interface ModelXYZ <>
Interface ObservableList <>
-Class AddressBook
-Class ReadOnlyAddressBook
-Class Model
-Class ModelManager
+Class XYZBook
+Class ReadOnlyXYZBook
+Class ModelXYZ
+Class ModelXYZManager
Class UserPrefs
-Class ReadOnlyUserPrefs
+Interface ReadOnlyUserPrefs <>
+
+Package Util {
+Class DateTime
+Class Title
+Class Time
+Class Description
+Class Contacts
+}
Package Person {
Class Person
-Class Address
+Class Role
Class Email
Class Name
Class Phone
Class UniquePersonList
}
-Package Tag {
-Class Tag
+Package Meeting {
+Class From
+Class To
+Class Location
+Class Meeting
+Class UniqueMeetingList
+}
+
+Package Deliverable {
+Class Deadline
+Class Milestone
+Class Deliverable
+Class UniqueDeliverableList
+}
+
+Package Event {
+interface TimeEvent <>
}
+
}
Class HiddenOutside #FFFFFF
-HiddenOutside ..> Model
+HiddenOutside ..> ModelXYZ
-AddressBook .up.|> ReadOnlyAddressBook
+XYZBook .up.|> ReadOnlyXYZBook
-ModelManager .up.|> Model
-Model .right.> ObservableList
-ModelManager o--> "1" AddressBook
-ModelManager o-left-> "1" UserPrefs
+ModelXYZManager .up.|> ModelXYZ
+ModelXYZ .right.> ObservableList
+ModelXYZManager o--> "1" XYZBook
+ModelXYZManager o-left-> "1" UserPrefs
UserPrefs .up.|> ReadOnlyUserPrefs
+note right of ModelXYZManager: XYZ = Deliverable, Meeting or Person
-AddressBook *--> "1" UniquePersonList
+XYZBook *--> "1" UniquePersonList
UniquePersonList o--> "*" Person
Person *--> Name
Person *--> Phone
Person *--> Email
-Person *--> Address
-Person *--> "*" Tag
+Person *--> Role
+Person *--> Description
+
+Role -[hidden]right-> Name
+Name -[hidden]right-> Email
+Email -[hidden]right-> Phone
+Phone -[hidden]right-> Description
+
+XYZBook *--> "1" UniqueMeetingList
+UniqueMeetingList o--> "*" Meeting
+Meeting .up.|> TimeEvent
+Meeting *--> From
+Meeting *--> To
+Meeting *--> Location
+Meeting *--> Title
+Meeting *--> Description
+Meeting *--> Contacts
+
+Location -[hidden]right-> To
+To -[hidden]right-> From
+
+From -down-|> DateTime
+To -down-|> Time
+
+XYZBook *--> "1" UniqueDeliverableList
+UniqueDeliverableList o--> "*" Deliverable
+Deliverable .up.|> TimeEvent
+Deliverable *--> Deadline
+Deliverable *--> Milestone
+Deliverable *--> Title
+Deliverable *--> Description
+Deliverable *--> Contacts
-Name -[hidden]right-> Phone
-Phone -[hidden]right-> Address
-Address -[hidden]right-> Email
+Deadline -down-|> DateTime
-ModelManager -->"1" Person : filtered list
+ModelXYZManager -->"1" Person : filtered list
+ModelXYZManager -->"1" Person : in view
+ModelXYZManager -->"1" Meeting : in view
+ModelXYZManager -->"1" Meeting : filtered list
+ModelXYZManager -->"1" Deliverable : filtered list
+ModelXYZManager -->"1" Deliverable : in view
@enduml
diff --git a/docs/diagrams/OCPSequenceDiagram.puml b/docs/diagrams/OCPSequenceDiagram.puml
new file mode 100644
index 00000000000..fb3cd9b1fb3
--- /dev/null
+++ b/docs/diagrams/OCPSequenceDiagram.puml
@@ -0,0 +1,47 @@
+@startuml
+!include style.puml
+
+scale 1.5
+
+box UI UI_COLOR_T1
+participant ":MainWindow" as Main UI_COLOR
+participant ":ProjectCompletionStatusPanel" as Panel UI_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant "deliverableList:ObservableList" as List MODEL_COLOR
+end box
+
+[-> Main: switchDashboard()
+activate Main
+Main -> Main : switchMode(ModeEnum.DASHBOARD)
+activate Main
+
+Main -> Panel: updateOcp()
+activate Panel
+
+Panel -> List : size()
+activate List
+List --> Panel : totalNumDeliverables
+deactivate List
+
+Panel -> Panel : findNumCompletedDeliverables(deliverableList)
+activate Panel
+Panel --> Panel: numCompletedDeliverables
+deactivate Panel
+
+Panel -> Panel : getOcp(totalNumDeliverables, numCompletedDeliverables)
+activate Panel
+Panel --> Panel : overallCompletionPercentage
+deactivate Panel
+
+Panel --> Main
+deactivate Panel
+
+Main --> Main
+deactivate Main
+
+[<-- Main
+deactivate Main
+
+@enduml
diff --git a/docs/diagrams/SaveStorageSequenceDiagram.puml b/docs/diagrams/SaveStorageSequenceDiagram.puml
new file mode 100644
index 00000000000..2fbc29a8c0d
--- /dev/null
+++ b/docs/diagrams/SaveStorageSequenceDiagram.puml
@@ -0,0 +1,59 @@
+@startuml
+!include style.puml
+
+Scale 1.5
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicXYZ" as LogicXYZ LOGIC_COLOR
+end box
+
+box Storage STORAGE_COLOR_T1
+participant ":StorageXYZ" as StorageXYZ STORAGE_COLOR
+participant ":XYZBookStorage" as XYZBookStorage STORAGE_COLOR
+participant "json:JsonSerializableXYZBookStorage" as JsonSerializableXYZBookStorage STORAGE_COLOR
+participant "jsonItem:JsonAdaptedXYZ" as JsonAdaptedXYZ STORAGE_COLOR
+
+end box
+
+[-> LogicXYZ : execute(command)
+activate LogicXYZ
+
+LogicXYZ -> StorageXYZ : saveXYZ(XYZBook)
+activate StorageXYZ
+
+StorageXYZ -> XYZBookStorage : saveXYZ(XYZBook)
+activate XYZBookStorage
+
+
+create JsonSerializableXYZBookStorage
+XYZBookStorage -> JsonSerializableXYZBookStorage
+activate JsonSerializableXYZBookStorage
+
+Note right
+XYZ = Deliverable,
+Meeting or Person
+end Note
+
+loop book has items
+ create JsonAdaptedXYZ
+ JsonSerializableXYZBookStorage -> JsonAdaptedXYZ
+ activate JsonAdaptedXYZ
+
+ JsonAdaptedXYZ --> JsonSerializableXYZBookStorage : jsonItem
+ deactivate JsonAdaptedXYZ
+
+end
+
+JsonSerializableXYZBookStorage --> XYZBookStorage : json
+deactivate JsonSerializableXYZBookStorage
+
+XYZBookStorage --> StorageXYZ
+deactivate XYZBookStorage
+
+StorageXYZ --> LogicXYZ
+deactivate StorageXYZ
+
+deactivate LogicXYZ
+[<--LogicXYZ
+
+@enduml
diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml
index 6adb2e156bf..0c21a771bc4 100644
--- a/docs/diagrams/StorageClassDiagram.puml
+++ b/docs/diagrams/StorageClassDiagram.puml
@@ -1,24 +1,30 @@
@startuml
!include style.puml
+
+scale 1.5
+
skinparam arrowThickness 1.1
skinparam arrowColor STORAGE_COLOR
skinparam classBackgroundColor STORAGE_COLOR
-Interface Storage <>
+package Storage{
+Interface XYZStorage <>
Interface UserPrefsStorage <>
-Interface AddressBookStorage <>
+Interface XYZBookStorage <>
-Class StorageManager
+Class StorageXYZManager
Class JsonUserPrefsStorage
-Class JsonAddressBookStorage
+Class JsonXYZBookStorage
-StorageManager .left.|> Storage
-StorageManager o-right-> UserPrefsStorage
-StorageManager o--> AddressBookStorage
+StorageXYZManager .left.|> XYZStorage
+StorageXYZManager o-right-> UserPrefsStorage
+StorageXYZManager o--> XYZBookStorage
JsonUserPrefsStorage .left.|> UserPrefsStorage
-JsonAddressBookStorage .left.|> AddressBookStorage
-JsonAddressBookStorage .down.> JsonSerializableAddressBookStorage
-JsonSerializableAddressBookStorage .right.> JsonSerializablePerson
-JsonSerializablePerson .right.> JsonAdaptedTag
+JsonXYZBookStorage .left.|> XYZBookStorage
+JsonXYZBookStorage .down.> JsonSerializableXYZBookStorage
+JsonSerializableXYZBookStorage .right.> JsonSerializableXYZ
+JsonSerializableXYZ .right.> JsonAdaptedXYZ
+note right of JsonXYZBookStorage : XYZ = Deliverable, \nMeeting or Person
+}
@enduml
diff --git a/docs/diagrams/SwitchModeActivityDiagram.puml b/docs/diagrams/SwitchModeActivityDiagram.puml
new file mode 100644
index 00000000000..dbdaf9c7957
--- /dev/null
+++ b/docs/diagrams/SwitchModeActivityDiagram.puml
@@ -0,0 +1,15 @@
+@startuml
+start
+:User executes command;
+
+'Since the beta syntax does not support placing the condition outside the
+'diamond we place it as the true branch instead.
+
+if () then ([valid command])
+ :Change mode in MainWindow;
+ :Change UI to new mode;
+else ([else])
+ :Throw ParseException;
+endif
+stop
+@enduml
diff --git a/docs/diagrams/SwitchModeMouseInputSequenceDiagram.puml b/docs/diagrams/SwitchModeMouseInputSequenceDiagram.puml
new file mode 100644
index 00000000000..441c3c3b3df
--- /dev/null
+++ b/docs/diagrams/SwitchModeMouseInputSequenceDiagram.puml
@@ -0,0 +1,20 @@
+@startuml
+!include style.puml
+
+box UI UI_COLOR_T1
+participant ":MainWindow" as MainWindow UI_COLOR
+end box
+
+[-> MainWindow : switchDeliverable()
+activate MainWindow
+
+MainWindow -> MainWindow: switchMode(ModeEnum.DELIVERABLE)
+
+activate MainWindow
+MainWindow --> MainWindow
+
+deactivate MainWindow
+[<--MainWindow
+deactivate MainWindow
+
+@enduml
diff --git a/docs/diagrams/SwitchModeSequenceDiagram.puml b/docs/diagrams/SwitchModeSequenceDiagram.puml
new file mode 100644
index 00000000000..8429a9d9c59
--- /dev/null
+++ b/docs/diagrams/SwitchModeSequenceDiagram.puml
@@ -0,0 +1,78 @@
+@startuml
+!include style.puml
+
+box UI UI_COLOR_T1
+participant ":MainWindow" as MainWindow UI_COLOR
+end box
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicDispatcherManager" as LogicDispatcherManager LOGIC_COLOR
+participant ":GeneralParser" as GeneralParser LOGIC_COLOR
+participant ":SwitchCommandParser" as SwitchCommandParser LOGIC_COLOR
+participant ":SwitchCommand" as SwitchCommand LOGIC_COLOR
+participant ":CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+[-> MainWindow : executeCommand("switch dv")
+activate MainWindow
+
+MainWindow -> LogicDispatcherManager : execute("switch dv", currMode)
+activate LogicDispatcherManager
+
+LogicDispatcherManager -> GeneralParser : parseCommand("switch dv")
+activate GeneralParser
+
+create SwitchCommandParser
+GeneralParser -> SwitchCommandParser: SwitchCommandParser()
+activate SwitchCommandParser
+SwitchCommandParser --> GeneralParser
+deactivate SwitchCommandParser
+
+GeneralParser -> SwitchCommandParser: parse("dv")
+activate SwitchCommandParser
+
+create SwitchCommand
+SwitchCommandParser -> SwitchCommand: SwitchCommand(DELIVERABLE)
+activate SwitchCommand
+SwitchCommand --> SwitchCommandParser
+deactivate SwitchCommand
+
+SwitchCommandParser --> GeneralParser
+deactivate SwitchCommandParser
+destroy SwitchCommandParser
+
+GeneralParser --> LogicDispatcherManager : command
+deactivate GeneralParser
+
+LogicDispatcherManager -> SwitchCommand : execute()
+activate SwitchCommand
+
+create CommandResult
+SwitchCommand -> CommandResult : CommandResult(..., DELIVERABLE)
+activate CommandResult
+CommandResult --> SwitchCommand
+deactivate CommandResult
+
+SwitchCommand --> LogicDispatcherManager : commandResult
+deactivate SwitchCommand
+destroy SwitchCommand
+SwitchCommand -[hidden]-> LogicDispatcherManager : commandResult
+
+LogicDispatcherManager --> MainWindow : commandResult
+deactivate LogicDispatcherManager
+
+MainWindow -> CommandResult : getMode()
+activate CommandResult
+CommandResult --> MainWindow : DELIVERABLE
+deactivate CommandResult
+destroy CommandResult
+
+MainWindow -> MainWindow: switchMode(DELIVERABLE)
+activate MainWindow
+MainWindow --> MainWindow
+
+deactivate MainWindow
+[<--MainWindow
+deactivate MainWindow
+
+@enduml
diff --git a/docs/diagrams/TimeEventClassDiagram.puml b/docs/diagrams/TimeEventClassDiagram.puml
new file mode 100644
index 00000000000..a5dd5fe7765
--- /dev/null
+++ b/docs/diagrams/TimeEventClassDiagram.puml
@@ -0,0 +1,50 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor MODEL_COLOR
+skinparam classBackgroundColor MODEL_COLOR
+
+Package Model <>{
+
+Class DateTime {
+ + value: LocalDateTime
+ + getLocalDateTime(): LocalDateTime
+}
+
+Class Meeting {
+ + getIndicatorTime(): LocalDateTime
+}
+
+Class Deadline {
+
+}
+
+class From {}
+Class Deliverable {
+
+ + getIndicatorTime(): LocalDateTime
+}
+
+interface TimeEvent <> {
+ getIndicatorTime(): LocalDateTime
+}
+}
+
+Meeting .up.|> TimeEvent
+Meeting *--> From
+
+From --|> DateTime
+
+Deliverable .up.|> TimeEvent
+Deliverable *--> Deadline
+
+Deadline --|> DateTime
+
+show DateTime methods
+show DateTime fields
+show Meeting methods
+show TimeEvent methods
+show Deliverable methods
+
+
+@enduml
diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml
index 92746f9fcf7..bc982dc7e6f 100644
--- a/docs/diagrams/UiClassDiagram.puml
+++ b/docs/diagrams/UiClassDiagram.puml
@@ -11,10 +11,19 @@ Class UiManager
Class MainWindow
Class HelpWindow
Class ResultDisplay
-Class PersonListPanel
-Class PersonCard
Class StatusBarFooter
Class CommandBox
+package XYZ <>{
+Class XYZDetailsPanel
+Class XYZListPanel
+Class XYZCard
+}
+package DashBoard <>{
+Class CalendarDeliverableCard
+Class CalendarListPanel
+Class CalendarMeetingCard
+Class ProjectCompletionStatusPanel
+}
}
package Model <> {
@@ -33,25 +42,34 @@ UiManager -down-> MainWindow
MainWindow --> HelpWindow
MainWindow *-down-> CommandBox
MainWindow *-down-> ResultDisplay
-MainWindow *-down-> PersonListPanel
+MainWindow *-down-> XYZListPanel
MainWindow *-down-> StatusBarFooter
+MainWindow *-down-> XYZDetailsPanel
+MainWindow *-down-> CalendarListPanel
+MainWindow *-down-> ProjectCompletionStatusPanel
-PersonListPanel -down-> PersonCard
+XYZListPanel -down-> XYZCard
+CalendarListPanel -down-> CalendarDeliverableCard
+CalendarListPanel -down-> CalendarMeetingCard
MainWindow -left-|> UiPart
ResultDisplay --|> UiPart
CommandBox --|> UiPart
-PersonListPanel --|> UiPart
-PersonCard --|> UiPart
+XYZListPanel --|> UiPart
+XYZCard --|> UiPart
StatusBarFooter --|> UiPart
HelpWindow -down-|> UiPart
+XYZDetailsPanel --|> UiPart
+CalendarListPanel --|> UiPart
+ProjectCompletionStatusPanel --|> UiPart
-PersonCard ..> Model
+XYZCard ..> Model
UiManager -right-> Logic
MainWindow -left-> Logic
+note top of XYZ: XYZ = Deliverable, Meeting or Person
-PersonListPanel -[hidden]left- HelpWindow
+XYZListPanel -[hidden]left- HelpWindow
HelpWindow -[hidden]left- CommandBox
CommandBox -[hidden]left- ResultDisplay
ResultDisplay -[hidden]left- StatusBarFooter
diff --git a/docs/diagrams/ViewCommandSequenceDiagram.puml b/docs/diagrams/ViewCommandSequenceDiagram.puml
new file mode 100644
index 00000000000..09db50d008e
--- /dev/null
+++ b/docs/diagrams/ViewCommandSequenceDiagram.puml
@@ -0,0 +1,74 @@
+@startuml
+!include style.puml
+
+box Logic LOGIC_COLOR_T1
+participant ":XYZLogicManager" as XYZLogicManager LOGIC_COLOR_T2
+participant ":XYZBookParser" as XYZBookParser LOGIC_COLOR_T2
+participant ":ViewCommandParser" as ViewCommandParser LOGIC_COLOR_T2
+participant "v:ViewCommand" as ViewCommand LOGIC_COLOR_T2
+end box
+
+note left of XYZLogicManager : XYZ = Deliverable, Meeting or Person
+
+box Model MODEL_COLOR_T1
+participant ":ModelXYZ" as ModelXYZ MODEL_COLOR_T2
+end box
+
+[-> XYZLogicManager : execute("view 2")
+activate XYZLogicManager
+
+XYZLogicManager -> XYZBookParser : parseCommand("view 2")
+activate XYZBookParser
+
+create ViewCommandParser
+XYZBookParser -> ViewCommandParser
+activate ViewCommandParser
+
+ViewCommandParser --> XYZBookParser
+deactivate ViewCommandParser
+
+XYZBookParser -> ViewCommandParser : parse("2")
+activate ViewCommandParser
+
+create ViewCommand
+ViewCommandParser -> ViewCommand
+activate ViewCommand
+
+ViewCommand --> ViewCommandParser : v
+deactivate ViewCommand
+
+ViewCommandParser --> XYZBookParser : v
+deactivate ViewCommandParser
+destroy ViewCommandParser
+
+XYZBookParser --> XYZLogicManager : v
+deactivate XYZBookParser
+
+XYZLogicManager -> ViewCommand : execute()
+activate ViewCommand
+
+ViewCommand -> ModelXYZ : setXYZInView(XYZ)
+activate ModelXYZ
+
+ModelXYZ --> ViewCommand
+deactivate ModelXYZ
+
+ViewCommand --> XYZLogicManager : result
+deactivate ViewCommand
+destroy ViewCommand
+
+[<-- XYZLogicManager : result
+deactivate XYZLogicManager
+
+[-> XYZLogicManager : getXYZInView()
+activate XYZLogicManager
+
+XYZLogicManager -> ModelXYZ : getXYZInView()
+activate ModelXYZ
+
+ModelXYZ --> XYZLogicManager : XYZ in view
+deactivate ModelXYZ
+
+[<-- XYZLogicManager : XYZ in view
+deactivate XYZLogicManager
+@enduml
diff --git a/docs/diagrams/style.puml b/docs/diagrams/style.puml
index fad8b0adeaa..d28ab9dcaa9 100644
--- a/docs/diagrams/style.puml
+++ b/docs/diagrams/style.puml
@@ -31,8 +31,12 @@
!define STORAGE_COLOR_T3 #806600
!define STORAGE_COLOR_T2 #544400
+!define CALENDAR_COLOR #7e009e
+
!define USER_COLOR #000000
+!define RANDOM_COLOR #FFA400
+
skinparam BackgroundColor #FFFFFFF
skinparam Shadowing false
@@ -69,6 +73,7 @@ skinparam ParticipantPadding 10
skinparam Shadowing false
skinparam DefaultTextAlignment center
skinparam packageStyle Rectangle
+skinparam classAttributeIconSize 0
hide footbox
hide members
diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png
index 2f1346869d0..fdee30699ed 100644
Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ
diff --git a/docs/images/ArchitectureSequenceDiagramSwitch.png b/docs/images/ArchitectureSequenceDiagramSwitch.png
new file mode 100644
index 00000000000..69f8241027e
Binary files /dev/null and b/docs/images/ArchitectureSequenceDiagramSwitch.png differ
diff --git a/docs/images/ArchitectureSequenceDiagramWithDb.png b/docs/images/ArchitectureSequenceDiagramWithDb.png
new file mode 100644
index 00000000000..7e74e677fcc
Binary files /dev/null and b/docs/images/ArchitectureSequenceDiagramWithDb.png differ
diff --git a/docs/images/AutosortSequenceDiagram.png b/docs/images/AutosortSequenceDiagram.png
new file mode 100644
index 00000000000..8db94a82a09
Binary files /dev/null and b/docs/images/AutosortSequenceDiagram.png differ
diff --git a/docs/images/CalendarSequenceDiagram.png b/docs/images/CalendarSequenceDiagram.png
new file mode 100644
index 00000000000..8fbfd21b987
Binary files /dev/null and b/docs/images/CalendarSequenceDiagram.png differ
diff --git a/docs/images/CommandSequenceDiagram.png b/docs/images/CommandSequenceDiagram.png
new file mode 100644
index 00000000000..9dbba11f934
Binary files /dev/null and b/docs/images/CommandSequenceDiagram.png differ
diff --git a/docs/images/Contact.png b/docs/images/Contact.png
new file mode 100644
index 00000000000..f3e7ffc90a1
Binary files /dev/null and b/docs/images/Contact.png differ
diff --git a/docs/images/ContactView.png b/docs/images/ContactView.png
new file mode 100644
index 00000000000..bd31be7a945
Binary files /dev/null and b/docs/images/ContactView.png differ
diff --git a/docs/images/DateTimeClass.png b/docs/images/DateTimeClass.png
new file mode 100644
index 00000000000..5417842f6a7
Binary files /dev/null and b/docs/images/DateTimeClass.png differ
diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png
deleted file mode 100644
index fa327b39618..00000000000
Binary files a/docs/images/DeleteSequenceDiagram.png and /dev/null differ
diff --git a/docs/images/Deliverable.png b/docs/images/Deliverable.png
new file mode 100644
index 00000000000..cf54a50a70d
Binary files /dev/null and b/docs/images/Deliverable.png differ
diff --git a/docs/images/DeliverableView.png b/docs/images/DeliverableView.png
new file mode 100644
index 00000000000..d981581b82b
Binary files /dev/null and b/docs/images/DeliverableView.png differ
diff --git a/docs/images/DoneCommandSequenceDiagram.png b/docs/images/DoneCommandSequenceDiagram.png
new file mode 100644
index 00000000000..eef60df8fb3
Binary files /dev/null and b/docs/images/DoneCommandSequenceDiagram.png differ
diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png
index b9e853cef12..0fa7c873984 100644
Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ
diff --git a/docs/images/Meeting.png b/docs/images/Meeting.png
new file mode 100644
index 00000000000..435dcc768bb
Binary files /dev/null and b/docs/images/Meeting.png differ
diff --git a/docs/images/MeetingView.png b/docs/images/MeetingView.png
new file mode 100644
index 00000000000..d724eae11da
Binary files /dev/null and b/docs/images/MeetingView.png differ
diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png
index 280064118cf..f9365cfe6d1 100644
Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ
diff --git a/docs/images/OCP.JPG b/docs/images/OCP.JPG
new file mode 100644
index 00000000000..eb8c44fc39e
Binary files /dev/null and b/docs/images/OCP.JPG differ
diff --git a/docs/images/OCPSequenceDiagram.png b/docs/images/OCPSequenceDiagram.png
new file mode 100644
index 00000000000..cf40d5d2ff7
Binary files /dev/null and b/docs/images/OCPSequenceDiagram.png differ
diff --git a/docs/images/SaveStorageSequenceDiagram.png b/docs/images/SaveStorageSequenceDiagram.png
new file mode 100644
index 00000000000..d2267add0d3
Binary files /dev/null and b/docs/images/SaveStorageSequenceDiagram.png differ
diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png
index d87c1216820..49392ed843b 100644
Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ
diff --git a/docs/images/SwitchModeActivityDiagram.png b/docs/images/SwitchModeActivityDiagram.png
new file mode 100644
index 00000000000..d9a867dd8d5
Binary files /dev/null and b/docs/images/SwitchModeActivityDiagram.png differ
diff --git a/docs/images/SwitchModeMouseInputSequenceDiagram.png b/docs/images/SwitchModeMouseInputSequenceDiagram.png
new file mode 100644
index 00000000000..689a4dd0f8d
Binary files /dev/null and b/docs/images/SwitchModeMouseInputSequenceDiagram.png differ
diff --git a/docs/images/SwitchModeSequenceDiagram.png b/docs/images/SwitchModeSequenceDiagram.png
new file mode 100644
index 00000000000..41769b9d573
Binary files /dev/null and b/docs/images/SwitchModeSequenceDiagram.png differ
diff --git a/docs/images/TimeEventClassDiagram.png b/docs/images/TimeEventClassDiagram.png
new file mode 100644
index 00000000000..f738f008c48
Binary files /dev/null and b/docs/images/TimeEventClassDiagram.png differ
diff --git a/docs/images/TimeEventDiagram.png b/docs/images/TimeEventDiagram.png
new file mode 100644
index 00000000000..c9eb5ebe638
Binary files /dev/null and b/docs/images/TimeEventDiagram.png differ
diff --git a/docs/images/Ui.png b/docs/images/Ui.png
index 5bd77847aa2..7508177a1de 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..20a02a14128 100644
Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ
diff --git a/docs/images/UiClassDiagramUpdated.png b/docs/images/UiClassDiagramUpdated.png
new file mode 100644
index 00000000000..94f08d31567
Binary files /dev/null and b/docs/images/UiClassDiagramUpdated.png differ
diff --git a/docs/images/UiLabel.jpg b/docs/images/UiLabel.jpg
new file mode 100644
index 00000000000..39e5ca826cc
Binary files /dev/null and b/docs/images/UiLabel.jpg differ
diff --git a/docs/images/ViewCommandSequenceDiagram.png b/docs/images/ViewCommandSequenceDiagram.png
new file mode 100644
index 00000000000..dafe9f04de1
Binary files /dev/null and b/docs/images/ViewCommandSequenceDiagram.png differ
diff --git a/docs/images/chrystalquek.png b/docs/images/chrystalquek.png
new file mode 100644
index 00000000000..0c10a25f254
Binary files /dev/null and b/docs/images/chrystalquek.png differ
diff --git a/docs/images/claraadora.png b/docs/images/claraadora.png
new file mode 100644
index 00000000000..64f57ab1f71
Binary files /dev/null and b/docs/images/claraadora.png differ
diff --git a/docs/images/gabztcr.png b/docs/images/gabztcr.png
new file mode 100644
index 00000000000..92ac1e40a49
Binary files /dev/null and b/docs/images/gabztcr.png differ
diff --git a/docs/images/helpMessage.JPG b/docs/images/helpMessage.JPG
new file mode 100644
index 00000000000..039528d87e4
Binary files /dev/null and b/docs/images/helpMessage.JPG differ
diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png
deleted file mode 100644
index b1f70470137..00000000000
Binary files a/docs/images/helpMessage.png and /dev/null differ
diff --git a/docs/images/merlinlim.png b/docs/images/merlinlim.png
new file mode 100644
index 00000000000..69043784c2c
Binary files /dev/null and b/docs/images/merlinlim.png differ
diff --git a/docs/images/shadowezz.png b/docs/images/shadowezz.png
new file mode 100644
index 00000000000..c398f8c3435
Binary files /dev/null and b/docs/images/shadowezz.png differ
diff --git a/docs/index.md b/docs/index.md
index 7601dbaad0d..8024271084e 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,19 +1,21 @@
---
layout: page
-title: AddressBook Level-3
+title: Productiv
---
-[](https://github.com/se-edu/addressbook-level3/actions)
-[](https://codecov.io/gh/se-edu/addressbook-level3)
+[](https://github.com/AY2021S1-CS2103T-F11-2/tp/actions)
+[](https://codecov.io/gh/AY2021S1-CS2103T-F11-2/tp)

-**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.
+**Productiv is a desktop application for product managers like yourself to organise your product-related information.**
+While it has a Graphical User
+Interface, most of your user interactions will happen through a Command Line Interface.
+Want to be **productive** in managing your **product**? Head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start) to get started!
+Otherwise, for those keen on developing Productiv, you may have a look at our [**Developer Guide**](DeveloperGuide.html).
**Acknowledgements**
-* Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5)
+* Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson),
+[JUnit5](https://github.com/junit-team/junit5), [fx-progress-circle](https://github.com/torakiki/fx-progress-circle/)
diff --git a/docs/team/chrystalquek.md b/docs/team/chrystalquek.md
new file mode 100644
index 00000000000..49c877a2137
--- /dev/null
+++ b/docs/team/chrystalquek.md
@@ -0,0 +1,62 @@
+---
+layout: page
+title: Chrystal Quek's Project Portfolio Page
+---
+
+### Project: Productiv
+
+Productiv is a desktop application for product managers to organise their product-related information. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 20 kLoC.
+
+Given below are my contributions to the project.
+
+**Code contributed**: [RepoSense](https://nus-cs2103-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=chrystalquek)
+
+##### New features and Enhancements
+
+* **New Feature**: Added the ability to switch modes.
+ * What it does: Allows the user to `switch` to the different modes of the app. The user can also click on the tabs in the navigation bar. Subsequent commands are executed with respect to the current mode.
+ * Justification: This feature improves the user experience significantly because the user can just choose to see only deliverable, meeting or contact related information. This makes the application less cluttered and more organised.
+
+ The user can also remember less commands, e.g. the `add` command word can be used to add a deliverable, meeting or contact, depending on the mode the user is currently in.
+ * Highlights: This feature was very difficult to implement and required significant restructuring of the entire application. I had to make the UI change whenever there was a switch in mode and ensure that the subsequent commands were passed to the correct `LogicManager`s for execution.
+
+* **Major enhancement**: Updated Person to user-facing Contact that is more useful for product-management.
+ * What it does: Allows the user to CRUD contacts in Productiv.
+ * Justification: Helps users keep track of the developers and stakeholders that are involved in the development of the product.
+ * Highlights: Refactored Person to user-facing Contact. A Contact can have a `Role` (developer or stakeholder) and also a `Description`.
+ After careful consideration, decided to remove attributes that would not be important for product-management such as `Address` and `Tag`.
+
+* **Major enhancement**: Improved list UI. Created a table format for list UI and made sure to only display fields that are more important to the user.
+
+* **Enhancements to existing features**:
+ * Updated the GUI color scheme: [\#48](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/48)
+ * Edited GUI for overall cohesiveness: [\#119](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/119)
+
+
+##### Project management
+ * Organised and lead some weekly team meetings.
+ * Facilitated task delegation.
+ * Ensured that deadlines were met.
+
+##### Contributions to team-based tasks
+ * Created skeleton for switching modes: [\#32](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/32), [\#106](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/106)
+ * Necessary general code enhancements
+ * Changed product icon and name and renamed jar file: [\#119](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/119)
+ * Changed log file name: [\#188](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/188)
+ * Updated Person to user-facing Contact that is more useful for product-management: [\#37](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/37), [\#67](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/67)
+ * Enabled assertions in Gradle: [\#94](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/94)
+ * Clarified and followed up with reviewers of PE-D on behalf of team: [\#4](https://github.com/khoodehui/ped/issues/4), [\#3](https://github.com/zhaohuanqdcn/ped/issues/3)
+ * Standardized App feedback messages: [\#124](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/124)
+
+##### Community
+ * PRs reviewed (with non-trivial review comments): [\#105](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/105), [\#70](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/70), [#34](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/34)
+ * Total PRs reviewed: 20+, Total comments given: 80+
+ * Reported bugs and suggestions for other teams (during PE-D): [#11](https://github.com/chrystalquek/ped/issues/11), [#7](https://github.com/chrystalquek/ped/issues/7)
+
+##### Documentation
+ * User Guide:
+ * Updated documentation for contact feature: [\#124](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/124)
+ * Added documentation for dashboard feature : [\#216](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/216)
+ * Developer Guide:
+ * Added implementation details of the `switch` feature. Included UML diagrams `SwitchModeSequenceDiagram`, `SwitchModeActivityDiagram` and `SwitchModeMouseInputSequenceDiagram`: [\#241](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/241)
+ * Updated almost the entire Appendix, including Instructions for Manual Testing and Effort. Also introduced a neater structure to the Appendix: [\#229](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/229)
diff --git a/docs/team/claraadora.md b/docs/team/claraadora.md
new file mode 100644
index 00000000000..0d21e89908e
--- /dev/null
+++ b/docs/team/claraadora.md
@@ -0,0 +1,63 @@
+---
+layout: page
+title: Clara Adora's Project Portfolio Page
+---
+
+### Project: Productiv
+
+Productiv is a desktop application for product managers to organise their product-related information.
+The user interacts with it using a CLI, and it has a GUI created with JavaFX.
+It is written in Java, and has about 20 kLoC.
+
+Given below are my contributions to the project.
+
+**Code contributed**: [RepoSense](https://nus-cs2103-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=claraadora)
+
+##### New features and Enhancements
+
+* **New Feature**: Added the Calendar feature.
+ * What it does: displays all `Deliverable`s and `Meeting`s in a single sorted-by-time list in the dashboard
+ * Justification: allows product managers to check their deliverables and meetings together in one place
+ * Highlights: This new feature requires a thorough design analysis. Amendments and discussions were done to prevent couplings and preserve cohesiveness
+* **New Feature**: Added the auto-sorting of `Deliverable`s and `Meeting`s
+ * What it does: auto-sorts `Deliverable`s and `Meeting`s in ascending chronological order.
+ * Justification: allows product managers to view their most urgent deliverables and meetings quickly, in neat sorted-by-time lists
+ * Highlights: Before settling with the current sorting implementation, an in-depth analysis of the `PriorityQueue` and binary search implementations was done
+* **Enhancement**: Designed the current UI/UX of Productiv.
+ * What it does: enhances the UI/UX of Productiv to be slick, clean, and intuitive.
+ * Justification: Good UI/UX plays a significant role in user satisfaction and retention.
+ * Highlights: Designing the UI/UX is challenging but essential as Productiv has four modes.
+ Many designs were made and improved upon, before reaching the current clutter-free design.
+
+##### Project management
+* Organised and led some weekly team meetings.
+* Fascilitated and delegated tasks.
+* Ensured that tasks were completed on time.
+
+##### Contributions to team-based tasks
+* Restructured AB3 to allow the addition of the deliverable, meeting, and mode components
+[\#38](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/38)
+* Created the skeleton of the Meeting component
+[\#47](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/47)
+* Filled application with synchronized seed data that make sense and reflective of product managers' busy schedule
+[\#237](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/237)
+* Updated user and developer guides that are not specific to my responsibilities
+
+##### Community
+* Reported bugs and suggestions for a team [(link to issues)](https://github.com/claraadora/ped/issues)
+* Reviewed PRs with non-trivial comments (examples:
+[\#32](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/32),
+[\#98](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/98),
+[\#108](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/108),
+[\#52](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/52))
+* Total PRs reviewed: 20+, Total comments given: 80+
+
+##### Documentation
+* User Guide
+ * Wrote the Introduction section [\#296](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/269/files)
+ * Co-wrote the Meeting subsection in the Feature section
+ * Added and edited all diagrams [\#296](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/269/files)
+ * Wrote the Command Summary tables [\#194](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/194)
+* Developer Guide
+ * Updated the Architecture sequence diagram and Model component object diagram [\#242](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/242)
+ * Added the implementation details and diagrams of the Calendar and auto-sort feature [\#242](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/242)
diff --git a/docs/team/gabztcr.md b/docs/team/gabztcr.md
new file mode 100644
index 00000000000..19316b54f15
--- /dev/null
+++ b/docs/team/gabztcr.md
@@ -0,0 +1,66 @@
+---
+layout: page
+title: Gabriel Tan's Project Portfolio Page
+---
+
+## Project: Productiv
+
+Productiv is a one-stop desktop app for product managers like yourself to organise your contacts, deliverables and
+meetings, so that you can track your product’s development easily.
+The user interacts with it using a CLI, and it has a GUI created with JavaFX.
+It is written in Java, and has about 20 kLoC.
+
+Given below are my contributions to the project.
+
+**Code contributed**: [RepoSense](https://nus-cs2103-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=gabztcr)
+
+##### New features and enhancements
+
+* **New Feature**: Deliverables (specifically add, edit and delete commands).
+ * What it does: This feature allows the user to track their product's deliverables easily with basic CRUD features.
+ * Justification: This key feature improves the product significantly because the user is a product manager who needs to have his deliverables organised so that he can work better towards production deadlines.
+ * Highlights: While this mode has its own UI screen in Productiv, the deliverable list itself would eventually be referenced in the dashboard.
+ As such, the implementation had to be done carefully with forward-thinking to ensure that such referencing would be smooth and adhere to coding standards, e.g. abstractions.
+ * Credits: Most of the code was reused from the original AddressBook, less minor tweakings for deliverable fields and test cases.
+
+* **New Feature**: Overall Completion Percentage (OCP).
+ * What it does: This feature gives the user an overview of the progress of the product' development using a donut chart. It is found in the left panel of the dashboard.
+ * Justification: This feature enhances the product greatly because the user is able to immediately see how much of the product is completed and adjust their priorities accordingly to meet production deadlines.
+ * Highlights: The OCP's colour and animation were adjusted to suit the app and make its general UI more appealing to users.
+ Although the library and its tutorial were available, implementing it was very challenging since the code bases were very different.
+ * Credits: The third-party library [fx-progress-circle](https://github.com/torakiki/fx-progress-circle/) and its [tutorial](https://youtu.be/9SEE8UP17jo) were used to implement this feature.
+
+* **Enhancements to existing features**:
+ * Implement autosort feature for deliverables that sorts them chronologically by deadline (PR [\#123](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/123)).
+ * Disabled clearing/listing of empty deliverable, meeting or contact lists (PR [\#240](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/240)).
+ * Enabled `find` command to accommodate punctuations when matching keywords to deliverables, meetings or contacts (PR [\#250](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/250)).
+
+##### Contributions to team-based tasks
+ * Created a shortened URL link of the User Guide for the GUI's help window.
+ * Wrote additional tests for deliverables to increase coverage (PRs [\#112](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/112), [\#123](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/123)).
+ * Amended all error messages for the feedback box in Productiv (PR [\#228](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/228)).
+ * Amended the alignment of and ordering within the UI of Productiv (PR [\#256](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/256)).
+
+##### Project management
+
+ * Set up [GitHub Projects](https://github.com/AY2021S1-CS2103T-F11-2/tp/projects/1) to track issues and PRs for Productiv.
+ * Hosted two sprint planning meetings to sync up on issues and discuss technical solutions.
+ * Managed milestone `mid-v1.4`.
+ * Created a timetable for PR merging to minimise merge conflicts when the team was working on our documentation together.
+
+##### Documentation
+
+ * User Guide:
+ * Created the skeleton version from AddressBook (PR [\#29](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/29)).
+ * Added documentation for `list`, `find` and `clear` commands for contacts (PR [\#99](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/99)).
+ * Updated the notes about the command format under Features (PRs [\#214](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/214), [\#265](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/265)).
+ * Reordered the fields for all existing commands (PR [\#256](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/256)).
+ * Oversaw the general accuracy, consistency and formatting.
+ * Developer Guide:
+ * Added implementation details of the OCP feature (PR [\#107](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/107)).
+ * Amended User Stories and Use Cases (PR [\#265](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/265)).
+
+##### Community
+
+ * Reviewed PRs with non-trivial comments (PRs [\#188](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/188), [\#189](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/189)).
+ * Reported bugs and suggestions for other teams (examples: [1](https://github.com/gabztcr/ped/issues/7), [2](https://github.com/gabztcr/ped/issues/6), [3](https://github.com/gabztcr/ped/issues/5)).
diff --git a/docs/team/merlinlim.md b/docs/team/merlinlim.md
new file mode 100644
index 00000000000..e10f626a30a
--- /dev/null
+++ b/docs/team/merlinlim.md
@@ -0,0 +1,108 @@
+---
+layout: page
+title: Merlin Lim's Project Portfolio Page
+---
+
+### Project: Productiv
+
+Productiv is a desktop application for product managers to organise their product-related information.
+The user interacts with it using a CLI, and it has a GUI created with JavaFX.
+It is written in Java, and has about 20 kLoC.
+
+Given below are my contributions to the project.
+
+**Code contributed**:
+[RepoSense link](https://nus-cs2103-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=merlinlim)
+
+##### New features and Enhancements
+
+* **New Feature**: Delete command for Meeting
+ * Delete command for meeting allows users to delete meeting from list.
+ * This feature allows users to remove meetings which may have been done or cancelled.
+* **New Feature**: Edit command for meeting
+ * Edit command for meeting allows users to edit meeting from list.
+ * This feature allows users to edit a specific meeting when details have changed (e.g Time, Location, etc).
+* **New Feature**: Find command for Meeting
+ * Find command for meeting allows users to find a meeting based on a keyword.
+ * This feature allows users to search for a specific meeting more conveniently.
+* **New Feature**: List command for Meeting
+ * List command for meeting allows user to display all meetings.
+ * This feature allows users to the unfiltered list of meetings.
+* **New Feature**: Date and time verification
+ * Date and time parses inputs as `DateTime` and `Time` classes.
+ * This feature allows users to compare field-parameters that extend from these classes,
+ i.e the schedule panel in Dashboard and Auto-sort feature compares DateTime classes and organises items from earliest to latest.
+
+* **Enhancements to existing features**:
+ * Updated the GUI for Schedule Panel
+ * Updated the GUI for Meeting
+
+##### Project management
+ * Organised and led some weekly team meetings
+ * Delegated tasks.
+ * Ensured deliverables were met by the end of the week
+
+##### Contributions to team-based tasks
+* Create Delete command for Meeting:
+[\#44](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/44),
+[\#52](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/52)
+* Create Edit command for Meeting:
+[\75](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/75)
+* Create find command for Meeting:
+[\121](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/121)
+* Create list command for Meeting:
+[\121](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/121)
+* Create DateTime class for Meeting:
+[\#71](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/71)
+* Create Time class for Meeting:
+[\#211](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/211)
+* Update Meeting GUI:
+[\#230](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/230)
+* Implement Date and Time Verification for Meeting
+[\#73](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/73)
+* Update Schedule UI
+[#211](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/211)
+* Bug Fixes:
+[\#74](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/74),
+[\#100](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/100),
+[\#232](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/232)
+* Update Productiv Logger:
+[\#213](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/213)
+* Increase Meeting Code Coverage:
+[\111](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/111)
+
+##### Community
+ * PRs reviewed (with non-trivial review comments):
+ [\#77](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/77),
+ [\#99](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/99),
+ [\#19](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/228),
+ Total PR reviewed: 24, Total comments given: 100+
+ * Reported bugs and suggestions for other teams (during PE-D):
+ [\#1](https://github.com/MerlinLim/ped/issues/1)
+ [\#2](https://github.com/MerlinLim/ped/issues/2)
+ [\#3](https://github.com/MerlinLim/ped/issues/3)
+ [\#4](https://github.com/MerlinLim/ped/issues/4)
+
+##### Documentation
+ * User Guide:
+ * Updated documentation for `meeting`:
+ [\#128](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/128)
+ * Include a Glossary:
+ [\#128](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/128)
+ * Did cosmetic tweaks to existing documentation of `deliverable`, `meeting`
+ and ensured overall structure and clarity:
+ [\#231](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/231)
+ * Developer Guide:
+ * Updated Logic component description and diagrams, ie `LogicClassDiagram`
+ and `CommandSequenceDiagram`:
+ [\#267](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/267)
+ * Updated Storage component description and included new diagrams, ie
+ `StorageClassDiagram` and `SaveStorageSequenceDiagram`:
+ [\#267](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/267)
+ * Added implementation details of Date and Time verification with UML diagram.
+ [\#103](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/103)
+ * Updated Appendix: Target Profile, User stories, Use cases, Non-Functional Requirements and Glossary:
+ [\#31](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/31),
+ [\#45](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/45).
+ * Updated manual testing.
+ [\#103](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/103)
diff --git a/docs/team/shadowezz.md b/docs/team/shadowezz.md
new file mode 100644
index 00000000000..5dedbc5b459
--- /dev/null
+++ b/docs/team/shadowezz.md
@@ -0,0 +1,70 @@
+---
+layout: page
+title: Cao Wenjie's Project Portfolio Page
+---
+
+### Project: Productiv
+
+Productiv is a one-stop desktop app for product managers like yourself to organise your eliverables, meetings and contacts,
+so that you can track your product’s development easily.
+
+The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 20 kLoC.
+
+Given below are my contributions to the project.
+
+**Code contributed**: [RepoSense link](https://nus-cs2103-ay2021s1.github.io/tp-dashboard/#breakdown=true&search=shadowezz&sort=groupTitle&sortWithin=title&since=2020-08-14&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other)
+
+##### New Features and Enhancements
+
+* **New Feature**: Added the ability to complete deliverables.
+ * What it does: This allows the user to mark specific deliverables as completed after accomplishing them.
+ * Justification: This feature improves the product by allowing user to keep track of the completion status of different deliverables and
+ focus on those that are not completed yet.
+ * Highlights: This enhancement is used to display the project completion percentage in the dashboard (implemented by Gabriel) so
+ that the user can have a big picture of the progress of his/her product.
+
+* **New Feature**: Added the ability to mark deliverables as on-going.
+ * What it does: This allows the user to revert the completion status of deliverables from completed back to on-going.
+ * Justification: This feature complements the previous feature by providing the user with the flexibility of updating the completion
+ status of deliverables with changing requirements. It also allows easy amendments when the user accidentally completes the wrong deliverable,
+ so that he/she does not have to delete the original deliverable to create a new one.
+
+* **New Feature**: Added the view feature.
+ * What it does: This allows the user to view the full details of a specific deliverable, meeting or contact.
+ * Justification: This feature makes the application neater and more user-friendly as now the list of items in the left panel
+ can be succinct to show only what is important to the user. Full details of each item can be displayed on the right panel using
+ the view command based on user's discretion.
+ * Highlights: This feature is challenging to implement as it requires a good understanding of JavaFX to ensure that the display is
+ rendered properly. Furthermore, there are also many considerations regarding how the view panel should change with each command the user
+ inputs, which needs to be carefully thought out.
+
+**Major Enhancement**:
+ * Upgraded edit commands to allow clearing an optional field
+ * What it does: Allows the user to clear away information from an optional field of an existing deliverable, meeting or contact through the
+ edit command.
+ * Justification: As optional fields can be left empty, this feature will provide the user with the convenience of removing optional fields that
+ are no longer relevant, instead of having to delete the entire item and add it again.
+
+**Enhancements to existing features**:
+ * Upgraded deliverable to include a new milestone field. [\#72](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/72)
+ * Updated the GUI to display the milestone and completion status of a deliverable as tags. [\#55](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/55)
+
+##### Contributions to team-based tasks
+ * Created the skeleton code for deliverable. [\#35](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/35)
+ * Implemented find, list and clear commands for deliverable. [\#116](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/116)
+
+##### Project management
+ * Led two team meetings, which involves setting the agenda and documenting what was discussed.
+ * In charge of opening, closing and assigning issues for the earlier milestones.
+ * Reviewed and provided constructive feedback for teammates' PR. [\#211](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/211),
+ [\#229](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/229)
+
+##### Documentation
+ * User Guide:
+ * Added documentation for the commands `done`, `undone` and `view`. [\#195](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/195),
+ [\#260](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/260)
+ * Added some FAQs [\#126](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/126)
+ * Developer Guide:
+ * Added description and `UiClassDiagram` of the UI component. [\#253](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/253)
+ * Added implementation details of the `done` and `view` feature. Included UML diagrams `DoneCommandSequenceDiagram` and
+ `ViewCommandSequenceDiagram`. [\#253](https://github.com/AY2021S1-CS2103T-F11-2/tp/pull/253)
diff --git a/docs/tutorials/AddRemark.md b/docs/tutorials/AddRemark.md
deleted file mode 100644
index 6907e29456c..00000000000
--- a/docs/tutorials/AddRemark.md
+++ /dev/null
@@ -1,394 +0,0 @@
----
-layout: page
-title: "Tutorial: Adding a command"
----
-
-Let's walk you through the implementation of a new command — `remark`.
-
-This command allows users of the AddressBook application to add optional remarks to people in their address book and edit it if required. The command should have the following format:
-
-`remark INDEX r/REMARK` (e.g., `remark 2 r/Likes baseball`)
-
-We’ll assume that you have already set up the development environment as outlined in the Developer’s Guide.
-
-
-## Create a new `remark` command
-
-Looking in the `logic.command` package, you will notice that each existing command have their own class. All the commands inherit from the abstract class `Command` which means that they must override `execute()`. Each `Command` returns an instance of `CommandResult` upon success and `CommandResult#feedbackToUser` is printed to the `ResultDisplay`.
-
-Let’s start by creating a new `RemarkCommand` class in the `src/main/java/seedu/address/logic/command` directory.
-
-For now, let’s keep `RemarkCommand` as simple as possible and print some output. We accomplish that by returning a `CommandResult` with an accompanying message.
-
-**`RemarkCommand.java`:**
-
-``` java
-package seedu.address.logic.commands;
-
-import seedu.address.model.Model;
-
-/**
- * Changes the remark of an existing person in the address book.
- */
-public class RemarkCommand extends Command {
-
- public static final String COMMAND_WORD = "remark";
-
- @Override
- public CommandResult execute(Model model) {
- return new CommandResult("Hello from remark");
- }
-}
-```
-
-### Hook `RemarkCommand` into the application
-
-Now that we have our `RemarkCommand` ready to be executed, we need to update `AddressBookParser#parseCommand()` to recognize the `remark` keyword. Add the new command to the `switch` block by creating a new `case` that returns a new instance of `RemarkCommand`.
-
-You can refer to the changes in this [diff](https://github.com/se-edu/addressbook-level3/commit/35eb7286f18a029d39cb7a29df8f172a001e4fd8#diff-34ace715a8a8d2e5a66e71289f017b47).
-
-### Run the application
-
-Run `Main#main` and try out your new `RemarkCommand`. If everything went well, you should see something like this:
-
-
-
-## Change `RemarkCommand` to throw an exception
-
-While we have successfully printed a message to `ResultDisplay`, the command does not do what it is supposed to do. Let’s change the command to throw an `CommandException` to accurately reflect that our command is still a work in progress.
-
-
-
-Following the convention in other commands, we add relevant messages as constants and use them.
-
-**`RemarkCommand.java`:**
-
-``` java
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the remark of the person identified "
- + "by the index number used in the last person listing. "
- + "Existing remark will be overwritten by the input.\n"
- + "Parameters: INDEX (must be a positive integer) "
- + "r/ [REMARK]\n"
- + "Example: " + COMMAND_WORD + " 1 "
- + "r/ Likes to swim.";
-
- public static final String MESSAGE_NOT_IMPLEMENTED_YET = "Remark command not implemented yet";
-
- @Override
- public CommandResult execute(Model model) throws CommandException {
- throw new CommandException(MESSAGE_NOT_IMPLEMENTED_YET);
- }
-```
-
-## Enhancing `RemarkCommand`
-
-Let’s change `RemarkCommand` to parse input from the user.
-
-### Make the command accept parameters
-
-We start by modifying the constructor of `RemarkCommand` to accept an `Index` and a `String`. While we are at it, let’s change the error message to echo the values. While this is not a replacement for tests, it is an obvious way to tell if our code is functioning as intended.
-
-``` java
-import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
-//...
-public class RemarkCommand extends Command {
- //...
- public static final String MESSAGE_ARGUMENTS = "Index: %1$d, Remark: %2$s";
-
- private final Index index;
- private final String remark;
-
- /**
- * @param index of the person in the filtered person list to edit the remark
- * @param remark of the person to be updated to
- */
- public RemarkCommand(Index index, String remark) {
- requireAllNonNull(index, remark);
-
- this.index = index;
- this.remark = remark;
- }
- @Override
- public CommandResult execute(Model model) throws CommandException {
- throw new CommandException(String.format(MESSAGE_ARGUMENTS, index.getOneBased(), remark));
- }
-
- @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);
- }
-}
-```
-
-Your code should look something like [this](https://github.com/se-edu/addressbook-level3/commit/35eb7286f18a029d39cb7a29df8f172a001e4fd8#diff-34ace715a8a8d2e5a66e71289f017b47) after you are done.
-
-### Parse user input
-
-Now let’s move on to writing a parser that will extract the index and remark from the input provided by the user.
-
-Create a `RemarkCommandParser` class in the `seedu.address.logic.parser` package. The class must extend the `Parser` interface.
-
-
-
-Thankfully, `ArgumentTokenizer#tokenize()` makes it trivial to parse user input. Let’s take a look at the JavaDoc provided for the function to understand what it does.
-
-**`ArgumentTokenizer.java`:**
-
-``` java
-/**
- * Tokenizes an arguments string and returns an {@code ArgumentMultimap}
- * object that maps prefixes to their respective argument values. Only the
- * given prefixes will be recognized in the arguments string.
- *
- * @param argsString Arguments string of the form:
- * {@code preamble value value ...}
- * @param prefixes Prefixes to tokenize the arguments string with
- * @return ArgumentMultimap object that maps prefixes to their
- * arguments
- */
-```
-
-We can tell `ArgumentTokenizer#tokenize()` to look out for our new prefix `r/` and it will return us an instance of `ArgumentMultimap`. Now let’s find out what we need to do in order to obtain the Index and String that we need. Let’s look through `ArgumentMultimap` :
-
-**`ArgumentMultimap.java`:**
-
-``` java
-/**
- * Returns the last value of {@code prefix}.
- */
-public Optional getValue(Prefix prefix) {
- List values = getAllValues(prefix);
- return values.isEmpty() ? Optional.empty() :
- Optional.of(values.get(values.size() - 1));
-}
-```
-
-This appears to be what we need to get a String of the remark. But what about the Index? Let's take a quick peek at existing `Command` that uses an index to see how it is done.
-
-**`DeleteCommandParser.java`:**
-
-``` java
-Index index = ParserUtil.parseIndex(args);
-return new DeleteCommand(index);
-```
-
-There appears to be another utility class that obtains an `Index` from the input provided by the user.
-
-Now that we have the know-how to extract the data that we need from the user’s input, we can parse the user command and create a new instance of `RemarkCommand`, as given below.
-
-**`RemarkCommandParser.java`:**
-
-``` java
-public RemarkCommand parse(String args) throws ParseException {
- requireNonNull(args);
- ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args,
- 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);
- }
-
- String remark = argMultimap.getValue(PREFIX_REMARK).orElse("");
-
- return new RemarkCommand(index, remark);
-}
-```
-
-
-
-:information_source: Don’t forget to update `AddressBookParser` to use our new `RemarkCommandParser`!
-
-
-
-If you are stuck, check out the sample
-[here](https://github.com/se-edu/addressbook-level3/commit/dc6d5139d08f6403da0ec624ea32bd79a2ae0cbf#diff-fc19ecee89c3732a62fbc8c840250508).
-
-## Add `Remark` to the model
-
-Now that we have all the information that we need, let’s lay the groundwork for propagating the remarks added into the in-memory storage of person data. We achieve that by working with the `Person` model. Each field in a Person is implemented as a separate class (e.g. a `Name` object represents the person’s name). That means we should add a `Remark` class so that we can use a `Remark` object to represent a remark given to a person.
-
-### Add a new `Remark` class
-
-Create a new `Remark` in `seedu.address.model.person`. Since a `Remark` is a field that is similar to `Address`, we can reuse a significant bit of code.
-
-A copy-paste and search-replace later, you should have something like [this](https://github.com/se-edu/addressbook-level3/commit/4516e099699baa9e2d51801bd26f016d812dedcc#diff-af2f075d24dfcd333876f0fbce321f25). Note how `Remark` has no constrains and thus does not require input
-validation.
-
-### Make use of `Remark`
-
-Let’s change `RemarkCommand` and `RemarkCommandParser` to use the new `Remark` class instead of plain `String`. These should be relatively simple changes.
-
-## Add a placeholder element for remark to the UI
-
-Without getting too deep into `fxml`, let’s go on a 5 minute adventure to get some placeholder text to show up for each person.
-
-Simply add the following to [`seedu.address.ui.PersonCard`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-0c6b6abcfac8c205e075294f25e851fe).
-
-**`PersonCard.java`:**
-
-``` java
-@FXML
-private Label remark;
-```
-
-
-`@FXML` is an annotation that marks a private or protected field and makes it accessible to FXML. It might sound like Greek to you right now, don’t worry — we will get back to it later.
-
-Then insert the following into [`main/resources/view/PersonListCard.fxml`](https://github.com/se-edu/addressbook-level3/commit/850b78879582f38accb05dd20c245963c65ea599#diff-12580431f55d7880578aa4c16f249e71).
-
-**`PersonListCard.fxml`:**
-
-``` xml
-
-```
-
-That’s it! Fire up the application again and you should see something like this:
-
-
-
-## Modify `Person` to support a `Remark` field
-
-Since `PersonCard` displays data from a `Person`, we need to update `Person` to get our `Remark` displayed!
-
-### Modify `Person`
-
-We change the constructor of `Person` to take a `Remark`. We will also need to define new fields and accessors accordingly to store our new addition.
-
-### Update other usages of `Person`
-
-Unfortunately, a change to `Person` will cause other commands to break, you will have to modify these commands to use the updated `Person`!
-
-
-
-:bulb: Use the `Find Usages` feature in IntelliJ IDEA on the `Person` class to find these commands.
-
-
-
-Refer to [this commit](https://github.com/se-edu/addressbook-level3/commit/ce998c37e65b92d35c91d28c7822cd139c2c0a5c) and check that you have got everything in order!
-
-
-## Updating Storage
-
-AddressBook stores data by serializing `JsonAdaptedPerson` into `json` with the help of an external library — Jackson. Let’s update `JsonAdaptedPerson` to work with our new `Person`!
-
-While the changes to code may be minimal, the test data will have to be updated as well.
-
-
-
-:exclamation: You must delete AddressBook’s storage file located at `/data/addressbook.json` before running it! Not doing so will cause AddressBook to default to an empty address book!
-
-
-
-Check out [this commit](https://github.com/se-edu/addressbook-level3/commit/556cbd0e03ff224d7a68afba171ad2eb0ce56bbf)
-to see what the changes entail.
-
-## Finalizing the UI
-
-Now that we have finalized the `Person` class and its dependencies, we can now bind the `Remark` field to the UI.
-
-Just add [this one line of code!](https://github.com/se-edu/addressbook-level3/commit/5b98fee11b6b3f5749b6b943c4f3bd3aa049b692)
-
-**`PersonCard.java`:**
-
-``` java
-public PersonCard(Person person, int displayedIndex) {
- //...
- remark.setText(person.getRemark().value);
-}
-```
-
-
-
-## Putting everything together
-
-After the previous step, we notice a peculiar regression — we went from displaying something to nothing at all. However, this is expected behavior as we are yet to update the `RemarkCommand` to make use of the code we've been adding in the last few steps.
-
-### Update `RemarkCommand` and `RemarkCommandParser`
-
-In this last step, we modify `RemarkCommand#execute()` to change the `Remark` of a `Person`. Since all fields in a `Person` are immutable, we create a new instance of a `Person` with the values that we want and
-save it with `Model#setPerson()`.
-
-**`RemarkCommand.java`:**
-
-``` java
-//...
- 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";
-//...
- @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.getAddress(), 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);
- }
-```
-
-
-
-## Writing tests
-
-Tests are crucial to ensuring that bugs don’t slip into the codebase unnoticed. This is especially true for large code bases where a change might lead to unintended behavior.
-
-Let’s verify the correctness of our code by writing some tests!
-
-### Automatically generating tests
-
-The goal is to write effective and efficient tests to ensure that `RemarkCommand#execute()` behaves as expected.
-
-The convention for test names is `methodName_testScenario_expectedResult`. An example would be
-`execute_filteredList_success`.
-
-Let’s create a test for `RemarkCommand#execute()` to test that adding a remark works. On `IntelliJ IDEA` you can bring up the context menu and choose to `Go To` \> `Test` or use the appropriate keyboard shortcut.
-
-
-
-Then, create a test for the `execute` method.
-
-
-
-Following convention, let’s change the name of the generated method to `execute_addRemarkUnfilteredList_success`.
-
-Let’s use the utility functions provided in `CommandTestUtil`. The functions ensure that commands produce the expected `CommandResult` and output the correct message. In this case, `CommandTestUtil#assertCommandSuccess` is the best fit as we are testing that a `RemarkCommand` will successfully add a `Remark`.
-
-You should end up with a test that looks something like [this](https://github.com/se-edu/addressbook-level3/commit/fac8f3fd855d55831ca0cc73313b5943d49d4d6e#diff-d749de38392f7ea504da7824641ba8d9).
-
-## Conclusion
-
-This concludes the tutorial for adding a new `Command` to AddressBook.
diff --git a/docs/tutorials/RemovingFields.md b/docs/tutorials/RemovingFields.md
deleted file mode 100644
index aa8e0baaad9..00000000000
--- a/docs/tutorials/RemovingFields.md
+++ /dev/null
@@ -1,103 +0,0 @@
----
-layout: page
-title: "Tutorial: Removing Fields"
----
-
-> Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.
->
-> — Antoine de Saint-Exupery
-
-When working on AddressBook, you will most likely find that some features and fields that are no longer necessary. In scenarios like this, you can consider refactoring the existing `Person` model to suit your use case.
-
-In this tutorial, we’ll do exactly just that and remove the `address` field from `Person`.
-
-* Table of Contents
-{:toc}
-
-## Safely deleting `Address`
-
-Fortunately, IntelliJ IDEA provides a robust refactoring tool that can identify *most* usages. Let’s try to use it as much as we can.
-
-### Assisted refactoring
-
-The `address` field in `Person` is actually an instance of the `seedu.address.model.person.Address` class. Since removing the `Address` class will break the application, we start by identifying `Address`'s usages. This allows us to see code that depends on `Address` to function properly and edit them on a case-by-case basis. Right-click the `Address` class and select `Refactor` \> `Safe Delete` through the menu.
-
-
-
-Choose to `View Usages` and you should be presented with a list of `Safe Delete Conflicts`. These conflicts describe locations in which the `Address` class is used.
-
-
-
-Remove usages of `Address` by performing `Safe Delete`s on each entry. You will need to exercise discretion when removing usages of `Address`. Functions like `ParserUtil#parseAddress()` can be safely removed but its usages must be removed as well. Other usages like in `EditPersonDescriptor` may require more careful inspection.
-
-Let’s try removing references to `Address` in `EditPersonDescriptor`.
-
-1. Safe delete the field `address` in `EditPersonDescriptor`.
-
-1. Select `Yes` when prompted to remove getters and setters.
-
-1. Select `View Usages` again.
- 
-
-1. Remove the usages of `address` and select `Do refactor` when you are done.
-
-
-
- :bulb: **Tip:** Removing usages may result in errors. Exercise discretion and fix them. For example, removing the `address` field from the `Person` class will require you to modify its constructor.
-
-
-1. Repeat the steps for the remaining usages of `Address`
-
-After you are done, verify that the application still works by compiling and running it again.
-
-### Manual refactoring
-
-Unfortunately, there are usages of `Address` that IntelliJ IDEA cannot identify. You can find them by searching for instances of the word `address` in your code (`Edit` \> `Find` \> `Find in path`).
-
-Places of interest to look out for would be resources used by the application. `main/resources` contains images and `fxml` files used by the application and `test/resources` contains test data. For example, there is a `$address` in each `PersonCard` that has not been removed nor identified.
-
-
-
-A quick look at the `PersonCard` class and its `fxml` file quickly reveals why it slipped past the automated refactoring.
-
-**`PersonCard.java`**
-
-``` java
-...
-@FXML
-private Label address;
-...
-```
-
-**`PersonCard.fxml`**
-
-``` xml
-...
-
-
-
-...
-```
-
-After removing the `Label`, we can proceed to formally test our code. If everything went well, you should have most of your tests pass. Fix any remaining errors until the tests all pass.
-
-## Tidying up
-
-At this point, your application is working as intended and all your tests are passing. What’s left to do is to clean up references to `Address` in test data and documentation.
-
-In `src/test/data/`, data meant for testing purposes are stored. While keeping the `address` field in the json files does not cause the tests to fail, it is not good practice to let cruft from old features accumulate.
-
-**`invalidPersonAddressBook.json`:**
-
-```json
-{
- "persons": [ {
- "name": "Person with invalid name field: Ha!ns Mu@ster",
- "phone": "9482424",
- "email": "hans@example.com",
- "address": "4th street"
- } ]
-}
-```
-
-You can go through each individual `json` file and manually remove the `address` field.
diff --git a/docs/tutorials/TracingCode.md b/docs/tutorials/TracingCode.md
deleted file mode 100644
index bd34ed498cd..00000000000
--- a/docs/tutorials/TracingCode.md
+++ /dev/null
@@ -1,250 +0,0 @@
----
-layout: page
-title: "Tutorial: Tracing code"
----
-
-> Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. …\[Therefore,\] making it easy to read makes it easier to write.
->
-> — Robert C. Martin Clean Code: A Handbook of Agile Software Craftsmanship
-
-When trying to understand an unfamiliar code base, one common strategy used is to trace some representative execution path through the code base. One easy way to trace an execution path is to use a debugger to step through the code. In this tutorial, you will be using the IntelliJ IDEA’s debugger to trace the execution path of a specific user command.
-
-* Table of Contents
-{:toc}
-
-## Before we start
-
-Before we jump into the code, it is useful to get an idea of the overall structure and the high-level behavior of the application. This is provided in the 'Architecture' section of the developer guide. In particular, the architecture diagram (reproduced below), tells us that the App consists of several components.
-
-
-
-It also has a sequence diagram (reproduced below) that tells us how a command propagates through the App.
-
-
-
-Note how the diagram shows only how the execution flows *between* the main components. That is, it does not show details of the execution path *inside* each component. By hiding those details, the diagram succeeds in informing the reader about the overall execution path of a command without overwhelming the reader with too much details. In this tutorial, you aim to find those omitted details so that you get a more in-depth understanding of the code base.
-
-Before we proceed, ensure that you have done the following:
-1. Read the [*Architecture* section of the DG](../DeveloperGuide.md#architecture)
-1. Set up the project in Intellij IDEA
-1. Learn basic debugging features of Intellij IDEA
-
-## Setting a break point
-
-As you know, the first step of debugging is to put in a breakpoint where you want the debugger to pause the execution. For example, if you are trying to understand how the App starts up, you would put a breakpoint in the first statement of the `main` method. In our case, we would want to begin the tracing at the very point where the App start processing user input (i.e., somewhere in the UI component), and then trace through how the execution proceeds through the UI component. However, the execution path through a GUI is often somewhat obscure due to various *event-driven mechanisms* used by GUI frameworks, which happens to be the case here too. Therefore, let us put the breakpoint where the UI transfers control to the Logic component. According to the sequence diagram, the UI component yields control to the Logic component through a method named `execute`. Searching through the code base for `execute()` yields a promising candidate in `seedu.address.ui.CommandBox.CommandExecutor`.
-
-
-
-A quick look at the class confirms that this is indeed close to what we’re looking for. However, it is just an `Interface`. Let’s delve further and find the implementation of the interface by using the `Find Usages` feature in IntelliJ IDEA.
-
-
-
-Bingo\! `MainWindow#executeCommand()` seems to be exactly what we’re looking for\!
-
-Now let’s set the breakpoint. First, double-click the item to reach the corresponding code. Once there, click on the left gutter to set a breakpoint, as shown below.
- 
-
-## Tracing the execution path
-
-Recall from the User Guide that the `edit` command has the format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…` For this tutorial we will be issuing the command `edit 1 n/Alice Yeoh`.
-
-
-
-:bulb: **Tip:** Over the course of the debugging session, you will encounter every major component in the application. Try to jot down what happens inside the component and where the execution transfers to another component.
-
-
-1. To start the debugging session, simply `Run` \> `Debug Main`
-
-1. Enter `edit 1 n/Alice Yeoh` into the command box and press `Enter`.
-
-1. The Debugger tool window should show up and look something like this:
- 
-
-1. Use the `Show execution point` feature to jump to the line of code that we stopped at:
- 
-
-1. `CommandResult commandResult = logic.execute(commandText);` is the line that you end up at.
-
-1. We are interested in the `logic.execute(commandText)` portion of that line so let’s `Step in` into that method call:
- 
-
-1. We end up in `LogicManager#execute()`. Let’s take a look at the body of the method and annotate what we can deduce.
-
- **LogicManager\#execute().**
-
- ``` java
- @Override
- public CommandResult execute(String commandText)
- throws CommandException, ParseException {
-
- //Logging, safe to ignore
- logger.info("----------------[USER COMMAND][" + commandText + "]");
-
- CommandResult commandResult;
- //Parse user input from String to a Command
- Command command = addressBookParser.parseCommand(commandText);
- //Executes the Command and stores the result
- commandResult = command.execute(model);
-
- try {
- //We can deduce that the previous line of code modifies model in some way
- // since it's being stored here.
- storage.saveAddressBook(model.getAddressBook());
- } catch (IOException ioe) {
- throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe);
- }
-
- return commandResult;
- }
- ```
-
-1. `LogicManager#execute()` appears to delegate most of the heavy lifting to other components. Let’s take a closer look at each one.
-
-1. `Step over` the logging code since it is of no interest to us now. 
-
-1. `Step into` the line where user input in parsed from a String to a Command.
-
- **`AddressBookParser\#parseCommand()`**
-
- ``` java
- public Command parseCommand(String userInput) throws ParseException {
- ...
- final String commandWord = matcher.group("commandWord");
- final String arguments = matcher.group("arguments");
- ...
- ```
-
-1. `Step over` until you reach the `switch` statement. The `Variables` window now shows the value of both `commandWord` and `arguments`:
- 
-
-1. We see that the value of `commandWord` is now `edit` but `arguments` is still not processed in any meaningful way.
-
-1. Stepping into the `switch`, we obviously stop at **`AddressBookParser\#parseCommand()`.**
-
- ``` java
- ...
- case EditCommand.COMMAND_WORD:
- return new EditCommandParser().parse(arguments);
- ...
- ```
-
-1. Let’s see what `EditCommandParser#parse()` does by stepping into it.
-
-1. Stepping through the method shows that it calls `ArgumentTokenizer#tokenize()` and `ParserUtil#parseIndex()` to obtain the arguments and index required.
-
-
:bulb: **Tip:** Sometimes you might end up stepping into functions that are not of interest. Simply `step out` of them\!
-
-
-1. The rest of the method seems to exhaustively check for the existence of each possible parameter of the `edit` command and store any possible changes in an `EditPersonDescriptor`. Recall that we can verify the contents of `editPersonDesciptor` through the `Variable` tool window.
- 
-
-1. Let’s continue stepping through until we return to `LogicManager#execute()`.
-
- The sequence diagram below shows the details of the execution path through the Logic component. Does the execution path you traced in the code so far matches with the diagram?
- 
-
-1. Now let’s see what happens when we call `command#execute()`\!
-
- **`EditCommand\#execute()`:**
-
- ``` java
- @Override
- public CommandResult execute(Model model) throws CommandException {
- ...
- Person personToEdit = lastShownList.get(index.getZeroBased());
- Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor);
- if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) {
- throw new CommandException(MESSAGE_DUPLICATE_PERSON);
- }
- model.setPerson(personToEdit, editedPerson);
- model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
- return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson));
- }
- ```
-
-1. As suspected, `command#execute()` does indeed make changes to `model`.
-
-1. We can a closer look at how storage works by repeatedly stepping into the code until we arrive at
- `JsonAddressBook#saveAddressBook()`.
-
-1. Again, it appears that the heavy lifting is delegated. Let’s take a look at `JsonSerializableAddressBook`'s constructor.
-
- **`JsonSerializableAddressBook\#JsonSerializableAddressBook()`:**
-
- ``` java
- /**
- * Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use.
- *
- * @param source future changes to this will not affect the created
- * {@code JsonSerializableAddressBook}.
- */
- public JsonSerializableAddressBook(ReadOnlyAddressBook source) {
- persons.addAll(
- source.getPersonList()
- .stream()
- .map(JsonAdaptedPerson::new)
- .collect(Collectors.toList()));
- }
- ```
-
-1. It appears that a `JsonAdaptedPerson` is created for each `Person` and then added to the `JsonSerializableAddressBook`.
-
-1. We can continue to step through until we return to `MainWindow#executeCommand()`.
-
-1. Stepping into `resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser());`, we end up in:
-
- **`ResultDisplay\#setFeedbackToUser()`**
-
- ``` java
- public void setFeedbackToUser(String feedbackToUser) {
- requireNonNull(feedbackToUser);
- resultDisplay.setText(feedbackToUser);
- }
- ```
-
-1. Finally, we step through until we reach the end of
- `MainWindow#executeCommand()`.
-
-## Conclusion
-
-In this tutorial, we traced a valid edit command from raw user input to
-the result being displayed to the user. From this tutorial, you learned
-more about the inner workings of AddressBook and how the various
-components mesh together to form one cohesive product.
-
-Here are some quick questions you can try to answer based on your
-execution path tracing. In some cases, you can do further tracing for
-the given commands to find exactly what happens.
-
-1. In this tutorial, we traced the "happy path" (i.e., no errors). What
- do you think will happen if we traced the following commands
- instead? What exceptions do you think will be thrown(if any), where
- will the exceptions be thrown and where will they be handled?
-
- 1. `redit 1 n/Alice Yu`
-
- 2. `edit 0 n/Alice Yu`
-
- 3. `edit 1 n/Alex Yeoh`
-
- 4. `edit 1`
-
- 5. `edit 1 n/アリス ユー`
-
- 6. `edit 1 t/one t/two t/three t/one`
-
-2. What components will you have to modify to perform the following
- enhancements to the application?
-
- 1. Make command words case-insensitive
-
- 2. Allow `delete` to remove more than one index at a time
-
- 3. Save the address book in the CSV format instead
-
- 4. Add a new command
-
- 5. Add a new field to `Person`
-
- 6. Add a new entity to the address book
diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java
index e5cfb161b73..955652d3dfc 100644
--- a/src/main/java/seedu/address/MainApp.java
+++ b/src/main/java/seedu/address/MainApp.java
@@ -13,21 +13,43 @@
import seedu.address.commons.exceptions.DataConversionException;
import seedu.address.commons.util.ConfigUtil;
import seedu.address.commons.util.StringUtil;
-import seedu.address.logic.Logic;
-import seedu.address.logic.LogicManager;
-import seedu.address.model.AddressBook;
-import seedu.address.model.Model;
-import seedu.address.model.ModelManager;
-import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.logic.LogicDeliverable;
+import seedu.address.logic.LogicDeliverableManager;
+import seedu.address.logic.LogicDispatcher;
+import seedu.address.logic.LogicDispatcherManager;
+import seedu.address.logic.LogicMeeting;
+import seedu.address.logic.LogicMeetingManager;
+import seedu.address.logic.LogicPerson;
+import seedu.address.logic.LogicPersonManager;
import seedu.address.model.ReadOnlyUserPrefs;
import seedu.address.model.UserPrefs;
+import seedu.address.model.deliverable.DeliverableBook;
+import seedu.address.model.deliverable.ModelDeliverable;
+import seedu.address.model.deliverable.ModelDeliverableManager;
+import seedu.address.model.deliverable.ReadOnlyDeliverableBook;
+import seedu.address.model.meeting.MeetingBook;
+import seedu.address.model.meeting.ModelMeeting;
+import seedu.address.model.meeting.ModelMeetingManager;
+import seedu.address.model.meeting.ReadOnlyMeetingBook;
+import seedu.address.model.person.AddressBook;
+import seedu.address.model.person.ModelPerson;
+import seedu.address.model.person.ModelPersonManager;
+import seedu.address.model.person.ReadOnlyAddressBook;
import seedu.address.model.util.SampleDataUtil;
-import seedu.address.storage.AddressBookStorage;
-import seedu.address.storage.JsonAddressBookStorage;
import seedu.address.storage.JsonUserPrefsStorage;
-import seedu.address.storage.Storage;
-import seedu.address.storage.StorageManager;
import seedu.address.storage.UserPrefsStorage;
+import seedu.address.storage.deliverable.DeliverableBookStorage;
+import seedu.address.storage.deliverable.JsonDeliverableBookStorage;
+import seedu.address.storage.deliverable.StorageDeliverable;
+import seedu.address.storage.deliverable.StorageDeliverableManager;
+import seedu.address.storage.meeting.JsonMeetingBookStorage;
+import seedu.address.storage.meeting.MeetingBookStorage;
+import seedu.address.storage.meeting.StorageMeeting;
+import seedu.address.storage.meeting.StorageMeetingManager;
+import seedu.address.storage.person.AddressBookStorage;
+import seedu.address.storage.person.JsonAddressBookStorage;
+import seedu.address.storage.person.StoragePerson;
+import seedu.address.storage.person.StoragePersonManager;
import seedu.address.ui.Ui;
import seedu.address.ui.UiManager;
@@ -36,19 +58,29 @@
*/
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);
protected Ui ui;
- protected Logic logic;
- protected Storage storage;
- protected Model model;
+ protected LogicPerson logicPerson;
+ protected StoragePerson storagePerson;
+ protected ModelPerson modelPerson;
+
+ protected ModelDeliverable modelDeliverable;
+ protected StorageDeliverable storageDeliverable;
+ protected LogicDeliverable logicDeliverable;
+
+ protected LogicMeeting logicMeeting;
+ protected StorageMeeting storageMeeting;
+ protected ModelMeeting modelMeeting;
+
+ protected LogicDispatcher logicDispatcher;
protected Config config;
@Override
public void init() throws Exception {
- logger.info("=============================[ Initializing AddressBook ]===========================");
+ logger.info("=============================[ Initializing Productiv ]===========================");
super.init();
AppParameters appParameters = AppParameters.parse(getParameters());
@@ -56,41 +88,113 @@ public void init() throws Exception {
UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath());
UserPrefs userPrefs = initPrefs(userPrefsStorage);
+
AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath());
- storage = new StorageManager(addressBookStorage, userPrefsStorage);
+ DeliverableBookStorage deliverableBookStorage = new JsonDeliverableBookStorage(
+ userPrefs.getDeliverableBookFilePath());
+ MeetingBookStorage meetingBookStorage = new JsonMeetingBookStorage(
+ userPrefs.getMeetingBookFilePath());
+ storagePerson = new StoragePersonManager(addressBookStorage, userPrefsStorage);
+ storageDeliverable = new StorageDeliverableManager(deliverableBookStorage, userPrefsStorage);
initLogging(config);
+ storageMeeting = new StorageMeetingManager(meetingBookStorage, userPrefsStorage);
- model = initModelManager(storage, userPrefs);
+ modelPerson = initModelManager(storagePerson, userPrefs);
+ modelDeliverable = initDeliverableModelManager(storageDeliverable, userPrefs);
+ modelMeeting = initMeetingModelManager(storageMeeting, userPrefs);
- logic = new LogicManager(model, storage);
+ logicPerson = new LogicPersonManager(modelPerson, storagePerson);
+ logicDeliverable = new LogicDeliverableManager(modelDeliverable, storageDeliverable);
+ logicMeeting = new LogicMeetingManager(modelMeeting, storageMeeting);
+ logicDispatcher = new LogicDispatcherManager(logicPerson, logicDeliverable, logicMeeting);
+
+ ui = new UiManager(logicDispatcher, logicPerson, logicDeliverable, logicMeeting);
- ui = new UiManager(logic);
}
/**
- * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}.
- * The data from the sample address book will be used instead if {@code storage}'s address book is not found,
- * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book.
+ * Returns a {@code ModelPerson} with the data from {@code storagePerson}'s address book and {@code userPrefs}.
+ * The data from the sample address book will be used instead if {@code storagePerson}'s address book is not found,
+ * or an empty address book will be used instead if errors occur when reading {@code storagePerson}'s address book.
*/
- private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) {
+ private ModelPerson initModelManager(StoragePerson storagePerson, ReadOnlyUserPrefs userPrefs) {
Optional addressBookOptional;
ReadOnlyAddressBook initialData;
try {
- addressBookOptional = storage.readAddressBook();
- if (!addressBookOptional.isPresent()) {
- logger.info("Data file not found. Will be starting with a sample AddressBook");
+ addressBookOptional = storagePerson.readAddressBook();
+ if (addressBookOptional.isEmpty()) {
+ logger.info("Data file for contact not found. Will be starting with a sample ContactBook");
}
initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook);
} catch (DataConversionException e) {
- logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook");
+ logger.warning("Data file not in the correct format. Will be starting with an empty ContactBook");
initialData = new AddressBook();
} catch (IOException e) {
- logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook");
+ logger.warning("Problem while reading from the file. Will be starting with an empty ContactBook");
initialData = new AddressBook();
}
- return new ModelManager(initialData, userPrefs);
+ return new ModelPersonManager(initialData, userPrefs);
+ }
+
+ /**
+ * Returns a {@code ModelDeliverable} with the data from {@code storageDeliverable}'s deliverable book and
+ * {@code userPrefs}. The data from the sample deliverable book will be used instead
+ * if {@code storageDeliverable}'s deliverable book is not found, or an empty deliverable book will be used instead
+ * if errors occur when reading {@code storageDeliverable}'s deliverable book.
+ */
+ private ModelDeliverable initDeliverableModelManager(StorageDeliverable storageDeliverable,
+ ReadOnlyUserPrefs userPrefs) {
+ Optional deliverableBookOptional;
+ ReadOnlyDeliverableBook initialData;
+ try {
+ deliverableBookOptional = storageDeliverable.readDeliverableBook();
+ if (!deliverableBookOptional.isPresent()) {
+ logger.info("Data file for deliverable not found. Will be starting with a sample DeliverableBook");
+ }
+ initialData = deliverableBookOptional.orElseGet(SampleDataUtil::getSampleDeliverableBook);
+ } catch (DataConversionException e) {
+ logger.warning("Data file for deliverable not in the correct format. "
+ + "Will be starting with an empty DeliverableBook");
+ initialData = new DeliverableBook();
+ } catch (IOException e) {
+ logger.warning("Problem while reading from the deliverable file. "
+ + "Will be starting with an empty DeliverableBook");
+ initialData = new DeliverableBook();
+ }
+
+ return new ModelDeliverableManager(initialData, userPrefs);
+ }
+
+ /**
+ * Returns a {@code ModelMeeting} with the data from {@code storageMeeting}'s meeting book and {@code userPrefs}.
+ * The data from the sample meeting book will be used instead if {@code storageMeeting}'s meeting book is
+ * not found, or an empty meeting book will be used instead if errors occur
+ * when reading {@code storageMeeting}'s meeting book.
+ */
+ private ModelMeeting initMeetingModelManager(StorageMeeting storageMeeting,
+ ReadOnlyUserPrefs userPrefs) {
+ Optional meetingBookOptional;
+ ReadOnlyMeetingBook initialData;
+
+ try {
+ meetingBookOptional = storageMeeting.readMeetingBook();
+ if (!meetingBookOptional.isPresent()) {
+ logger.info("Data file for meeting not found. Will be starting with a sample MeetingBook");
+ }
+ initialData = meetingBookOptional.orElseGet(SampleDataUtil::getSampleMeetingBook);
+ } catch (DataConversionException e) {
+ logger.warning("Data file for meeting not in the correct format. "
+ + "Will be starting with an empty MeetingBook");
+ initialData = new MeetingBook();
+ } catch (IOException e) {
+ logger.warning("Problem while reading from the meeting file. "
+ + "Will be starting with an empty MeetingBook");
+ initialData = new MeetingBook();
+ }
+
+ return new ModelMeetingManager(initialData, userPrefs);
}
private void initLogging(Config config) {
@@ -151,7 +255,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) {
+ "Using default user prefs");
initializedPrefs = new UserPrefs();
} catch (IOException e) {
- logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook");
+ logger.warning("Problem while reading from the file. Will be starting with an empty ContactBook");
initializedPrefs = new UserPrefs();
}
@@ -167,15 +271,15 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) {
@Override
public void start(Stage primaryStage) {
- logger.info("Starting AddressBook " + MainApp.VERSION);
+ logger.info("Starting Productiv " + MainApp.VERSION);
ui.start(primaryStage);
}
@Override
public void stop() {
- logger.info("============================ [ Stopping Address Book ] =============================");
+ logger.info("============================ [ Stopping Productiv ] =============================");
try {
- storage.saveUserPrefs(model.getUserPrefs());
+ storagePerson.saveUserPrefs(modelPerson.getUserPrefs());
} catch (IOException e) {
logger.severe("Failed to save preferences " + StringUtil.getDetails(e));
}
diff --git a/src/main/java/seedu/address/commons/ModeEnum.java b/src/main/java/seedu/address/commons/ModeEnum.java
new file mode 100644
index 00000000000..cf6f14e83a7
--- /dev/null
+++ b/src/main/java/seedu/address/commons/ModeEnum.java
@@ -0,0 +1,45 @@
+package seedu.address.commons;
+
+import static seedu.address.commons.util.StringUtil.getStringJoinedBySeparator;
+
+import java.util.Arrays;
+
+public enum ModeEnum {
+
+ DASHBOARD("Dashboard", "db"),
+ DELIVERABLE("Deliverable", "dv"),
+ MEETING("Meeting", "m"),
+ PERSON("Contact", "c");
+
+ private final String name;
+ private final String argument;
+
+ ModeEnum(String name, String argument) {
+ this.name = name;
+ this.argument = argument;
+ }
+
+ public static ModeEnum getEnumByArgument(String argument) {
+ for (ModeEnum modeEnum : ModeEnum.values()) {
+ if (modeEnum.argument.equals(argument)) {
+ return modeEnum;
+ }
+ }
+ return null;
+ }
+
+ public static String getModeOptions() {
+ return getStringJoinedBySeparator(Arrays.stream(ModeEnum.values())
+ .map(mode -> mode.getArgument() + " (" + mode.toString() + ")"), " or ");
+ }
+
+ public String getArgument() {
+ return argument;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+}
diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/seedu/address/commons/core/GuiSettings.java
index ba33653be67..005ea8bf136 100644
--- a/src/main/java/seedu/address/commons/core/GuiSettings.java
+++ b/src/main/java/seedu/address/commons/core/GuiSettings.java
@@ -10,8 +10,8 @@
*/
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 = 1000;
private final double windowWidth;
private final double windowHeight;
diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/address/commons/core/LogsCenter.java
index 431e7185e76..8d835ea2591 100644
--- a/src/main/java/seedu/address/commons/core/LogsCenter.java
+++ b/src/main/java/seedu/address/commons/core/LogsCenter.java
@@ -18,7 +18,7 @@
public class LogsCenter {
private static final int MAX_FILE_COUNT = 5;
private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB
- private static final String LOG_FILE = "addressbook.log";
+ private static final String LOG_FILE = "productiv.log";
private static Level currentLogLevel = Level.INFO;
private static final Logger logger = LogsCenter.getLogger(LogsCenter.class);
private static FileHandler fileHandler;
diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java
index 1deb3a1e469..b64afb4a941 100644
--- a/src/main/java/seedu/address/commons/core/Messages.java
+++ b/src/main/java/seedu/address/commons/core/Messages.java
@@ -5,9 +5,15 @@
*/
public class Messages {
- public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command";
+ public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command!";
public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s";
- public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid";
- public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!";
-
+ public static final String MESSAGE_INVALID_CONTACT_DISPLAYED_INDEX = "Invalid contact index!";
+ public static final String MESSAGE_INVALID_DELIVERABLE_DISPLAYED_INDEX = "Invalid deliverable index!";
+ public static final String MESSAGE_INVALID_MEETING_DISPLAYED_INDEX = "Invalid meeting index!";
+ public static final String MESSAGE_INVALID_CONTACT_LIST_EMPTY = "No contacts to %1$s!";
+ public static final String MESSAGE_INVALID_DELIVERABLE_LIST_EMPTY = "No deliverables to %1$s!";
+ public static final String MESSAGE_INVALID_MEETING_LIST_EMPTY = "No meetings to %1$s!";
+ public static final String MESSAGE_CONTACTS_LISTED_OVERVIEW = "Found %1$d contact(s)!";
+ public static final String MESSAGE_DELIVERABLES_LISTED_OVERVIEW = "Found %1$d deliverable(s)!";
+ public static final String MESSAGE_MEETINGS_LISTED_OVERVIEW = "Found %1$d meeting(s)!";
}
diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java
index 61cc8c9a1cb..a7a2e1e4563 100644
--- a/src/main/java/seedu/address/commons/util/StringUtil.java
+++ b/src/main/java/seedu/address/commons/util/StringUtil.java
@@ -6,6 +6,8 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* Helper functions for handling strings.
@@ -65,4 +67,14 @@ public static boolean isNonZeroUnsignedInteger(String s) {
return false;
}
}
+
+ /**
+ * Returns a String with elements in stream joined by separator.
+ * Use for validation regex, options, etc.
+ * @param stream Stream with elements of type String
+ * @return String with elements in stream joined by separator
+ */
+ public static final String getStringJoinedBySeparator(Stream stream, String separator) {
+ return stream.collect(Collectors.joining(separator));
+ }
}
diff --git a/src/main/java/seedu/address/logic/LogicDeliverable.java b/src/main/java/seedu/address/logic/LogicDeliverable.java
new file mode 100644
index 00000000000..a7cf075dd39
--- /dev/null
+++ b/src/main/java/seedu/address/logic/LogicDeliverable.java
@@ -0,0 +1,57 @@
+package seedu.address.logic;
+
+import java.nio.file.Path;
+
+import javafx.collections.ObservableList;
+import seedu.address.commons.core.GuiSettings;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.deliverable.ModelDeliverable;
+import seedu.address.model.deliverable.ReadOnlyDeliverableBook;
+import seedu.address.model.deliverable.deliverable.Deliverable;
+
+/**
+ * API of logic component for deliverable (should only have one interface for logic, change later)
+ */
+public interface LogicDeliverable {
+ /**
+ * Executes the command and returns the result.
+ * @param commandText The command as entered by the user.
+ * @return the result of the command execution.
+ * @throws CommandException If an error occurs during command execution.
+ * @throws ParseException If an error occurs during parsing.
+ */
+ CommandResult execute(String commandText) throws CommandException, ParseException;
+
+ /**
+ * Returns the DeliverableBook.
+ *
+ * @see ModelDeliverable#getDeliverableBook()
+ */
+ ReadOnlyDeliverableBook getDeliverableBook();
+
+ /** Returns an unmodifiable view of the filtered list of deliverables */
+ ObservableList getFilteredDeliverableList();
+
+ /** Returns the internal list of deliverables */
+ ObservableList getInternalDeliverableList();
+
+ /** Returns the deliverable that is currently in view */
+ Deliverable getDeliverableInView();
+
+ /**
+ * Returns the user prefs' deliverable book file path.
+ */
+ Path getDeliverableBookFilePath();
+
+ /**
+ * Returns the user prefs' GUI settings.
+ */
+ GuiSettings getGuiSettings();
+
+ /**
+ * Set the user prefs' GUI settings.
+ */
+ void setGuiSettings(GuiSettings guiSettings);
+}
diff --git a/src/main/java/seedu/address/logic/LogicDeliverableManager.java b/src/main/java/seedu/address/logic/LogicDeliverableManager.java
new file mode 100644
index 00000000000..c6a70de371c
--- /dev/null
+++ b/src/main/java/seedu/address/logic/LogicDeliverableManager.java
@@ -0,0 +1,89 @@
+package seedu.address.logic;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+import javafx.collections.ObservableList;
+import seedu.address.commons.core.GuiSettings;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.deliverable.Command;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.logic.parser.deliverable.DeliverableBookParser;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.deliverable.ModelDeliverable;
+import seedu.address.model.deliverable.ReadOnlyDeliverableBook;
+import seedu.address.model.deliverable.deliverable.Deliverable;
+import seedu.address.storage.deliverable.StorageDeliverable;
+
+
+/**
+ * Logic manager for deliverables
+ */
+public class LogicDeliverableManager implements LogicDeliverable {
+
+ public static final String FILE_OPS_ERROR_MESSAGE = "Could not save data to deliverable file: ";
+ private final ModelDeliverable modelDeliverable;
+ private final StorageDeliverable storageDeliverable;
+ private final DeliverableBookParser deliverableBookParser;
+
+ /**
+ * Constructs a {@code LogicDeliverableManager} with the given {@code ModelDeliverable}
+ * and {@code StorageDeliverable}.
+ */
+ public LogicDeliverableManager(ModelDeliverable modelDeliverable, StorageDeliverable storageDeliverable) {
+ this.modelDeliverable = modelDeliverable;
+ this.storageDeliverable = storageDeliverable;
+ this.deliverableBookParser = new DeliverableBookParser();
+ }
+
+ @Override
+ public CommandResult execute(String commandText) throws CommandException, ParseException {
+
+ CommandResult commandResult;
+ Command command = deliverableBookParser.parseCommand(commandText);
+ commandResult = command.execute(modelDeliverable);
+
+ try {
+ storageDeliverable.saveDeliverableBook(modelDeliverable.getDeliverableBook());
+ } catch (IOException ioe) {
+ throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe);
+ }
+
+ return commandResult;
+ }
+
+ @Override
+ public ReadOnlyDeliverableBook getDeliverableBook() {
+ return modelDeliverable.getDeliverableBook();
+ }
+
+ @Override
+ public ObservableList getFilteredDeliverableList() {
+ return modelDeliverable.getFilteredDeliverableList();
+ }
+
+ @Override
+ public ObservableList getInternalDeliverableList() {
+ return modelDeliverable.getInternalDeliverableList();
+ }
+
+ @Override
+ public Deliverable getDeliverableInView() {
+ return modelDeliverable.getDeliverableInView();
+ }
+
+ @Override
+ public Path getDeliverableBookFilePath() {
+ return modelDeliverable.getDeliverableBookFilePath();
+ }
+
+ @Override
+ public GuiSettings getGuiSettings() {
+ return modelDeliverable.getGuiSettings();
+ }
+
+ @Override
+ public void setGuiSettings(GuiSettings guiSettings) {
+ modelDeliverable.setGuiSettings(guiSettings);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/LogicDispatcher.java b/src/main/java/seedu/address/logic/LogicDispatcher.java
new file mode 100644
index 00000000000..dcee1db98eb
--- /dev/null
+++ b/src/main/java/seedu/address/logic/LogicDispatcher.java
@@ -0,0 +1,20 @@
+package seedu.address.logic;
+
+import seedu.address.commons.ModeEnum;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+public interface LogicDispatcher {
+ /**
+ * Executes the command and returns the result.
+ * @param commandText The command as entered by the user.
+ * @param mode
+ * @return the result of the command execution.
+ * @throws CommandException If an error occurs during command execution.
+ * @throws ParseException If an error occurs during parsing.
+ */
+ CommandResult execute(String commandText, ModeEnum mode) throws CommandException, ParseException;
+
+ boolean isGeneralCommand(String commandText) throws ParseException;
+}
diff --git a/src/main/java/seedu/address/logic/LogicDispatcherManager.java b/src/main/java/seedu/address/logic/LogicDispatcherManager.java
new file mode 100644
index 00000000000..6f5fa479174
--- /dev/null
+++ b/src/main/java/seedu/address/logic/LogicDispatcherManager.java
@@ -0,0 +1,76 @@
+package seedu.address.logic;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.logging.Logger;
+
+import seedu.address.commons.ModeEnum;
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.logic.commands.general.Command;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.logic.parser.general.GeneralParser;
+
+/**
+ * The main LogicManager of the app.
+ */
+public class LogicDispatcherManager implements LogicDispatcher {
+ private final Logger logger = LogsCenter.getLogger(LogicDispatcherManager.class);
+ private final GeneralParser generalParser;
+
+ private final LogicPerson logicPerson;
+ private final LogicDeliverable logicDeliverable;
+ private final LogicMeeting logicMeeting;
+
+ /**
+ * Main Dispatcher of the app to the correct Logic Manager.
+ *
+ * @param logicPerson
+ * @param logicDeliverable
+ * @param logicMeeting
+ */
+ public LogicDispatcherManager(LogicPerson logicPerson, LogicDeliverable logicDeliverable,
+ LogicMeeting logicMeeting) {
+ this.generalParser = new GeneralParser();
+ this.logicPerson = logicPerson;
+ this.logicDeliverable = logicDeliverable;
+ this.logicMeeting = logicMeeting;
+ }
+
+ @Override
+ public CommandResult execute(String commandText, ModeEnum mode) throws CommandException, ParseException {
+ logger.info("----------------[USER COMMAND][" + commandText + "]");
+
+ CommandResult commandResult = null;
+
+ // catch all general commands and pass to modeParser
+ if (isGeneralCommand(commandText) || mode == ModeEnum.DASHBOARD) {
+ Command command = generalParser.parseCommand(commandText);
+ commandResult = command.execute();
+ } else {
+ switch (mode) {
+ case PERSON:
+ commandResult = logicPerson.execute(commandText);
+ break;
+ case DELIVERABLE:
+ commandResult = logicDeliverable.execute(commandText);
+ break;
+ case MEETING:
+ commandResult = logicMeeting.execute(commandText);
+ break;
+ default:
+ assert false : "from default: " + ModeEnum.getModeOptions();
+ }
+ }
+
+ requireNonNull(commandResult);
+
+ return commandResult;
+ }
+
+ public boolean isGeneralCommand(String commandText) throws ParseException {
+ return generalParser.isGeneralCommand(commandText);
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/LogicMeeting.java b/src/main/java/seedu/address/logic/LogicMeeting.java
new file mode 100644
index 00000000000..49a8438079f
--- /dev/null
+++ b/src/main/java/seedu/address/logic/LogicMeeting.java
@@ -0,0 +1,60 @@
+package seedu.address.logic;
+
+import java.nio.file.Path;
+
+import javafx.collections.ObservableList;
+import seedu.address.commons.core.GuiSettings;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.meeting.ModelMeeting;
+import seedu.address.model.meeting.ReadOnlyMeetingBook;
+import seedu.address.model.meeting.meeting.Meeting;
+
+/**
+ * API of logic component for meeting
+ */
+public interface LogicMeeting {
+ /**
+ * Executes the command and returns the result.
+ * @param commandText The command as entered by the user.
+ * @return the result of the command execution.
+ * @throws CommandException If an error occurs during command execution.
+ * @throws ParseException If an error occurs during parsing.
+ */
+ CommandResult execute(String commandText) throws CommandException, ParseException;
+
+ /**
+ * Returns the MeetingBook.
+ *
+ * @see ModelMeeting#getMeetingBook()
+ */
+ ReadOnlyMeetingBook getMeetingBook();
+
+ /**
+ * Returns the meeting that is currently in view.
+ */
+ Meeting getMeetingInView();
+
+ /** Returns an unmodifiable view of the filtered list of Meetings */
+ ObservableList getFilteredMeetingList();
+
+ /** Returns an internal list of Meetings */
+ ObservableList getInternalMeetingList();
+
+ /**
+ * Returns the user prefs' meeting book file path.
+ */
+ Path getMeetingBookFilePath();
+
+ /**
+ * Returns the user prefs' GUI settings.
+ */
+ GuiSettings getGuiSettings();
+
+ /**
+ * Set the user prefs' GUI settings.
+ */
+ void setGuiSettings(GuiSettings guiSettings);
+
+}
diff --git a/src/main/java/seedu/address/logic/LogicMeetingManager.java b/src/main/java/seedu/address/logic/LogicMeetingManager.java
new file mode 100644
index 00000000000..803e0a2604a
--- /dev/null
+++ b/src/main/java/seedu/address/logic/LogicMeetingManager.java
@@ -0,0 +1,88 @@
+package seedu.address.logic;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+import javafx.collections.ObservableList;
+import seedu.address.commons.core.GuiSettings;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.logic.commands.meeting.Command;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.logic.parser.meeting.MeetingBookParser;
+import seedu.address.model.meeting.ModelMeeting;
+import seedu.address.model.meeting.ReadOnlyMeetingBook;
+import seedu.address.model.meeting.meeting.Meeting;
+import seedu.address.storage.meeting.StorageMeeting;
+
+/**
+ * Manages the logic for the meeting feature.
+ */
+public class LogicMeetingManager implements LogicMeeting {
+
+ public static final String FILE_OPS_ERROR_MESSAGE = "Could not save data to meeting file: ";
+ private final ModelMeeting modelMeeting;
+ private final StorageMeeting storageMeeting;
+ private final MeetingBookParser meetingBookParser;
+
+ /**
+ * Constructs a {@code LogicMeetingManager} with the given {@code ModelMeeting} and {@code Storage}.
+ */
+ public LogicMeetingManager(ModelMeeting modelMeeting, StorageMeeting storageMeeting) {
+ this.modelMeeting = modelMeeting;
+ this.storageMeeting = storageMeeting;
+ this.meetingBookParser = new MeetingBookParser();
+ }
+
+ @Override
+ public CommandResult execute(String commandText) throws CommandException, ParseException {
+
+ CommandResult commandResult;
+ Command command = meetingBookParser.parseCommand(commandText);
+ commandResult = command.execute(modelMeeting);
+
+ try {
+ storageMeeting.saveMeetingBook(modelMeeting.getMeetingBook());
+ } catch (IOException ioe) {
+ throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe);
+ }
+
+ return commandResult;
+ }
+
+ @Override
+ public ReadOnlyMeetingBook getMeetingBook() {
+ return modelMeeting.getMeetingBook();
+ }
+
+ @Override
+ public Meeting getMeetingInView() {
+ return modelMeeting.getMeetingInView();
+ }
+
+ @Override
+ public ObservableList getFilteredMeetingList() {
+ return modelMeeting.getFilteredMeetingList();
+ }
+
+ @Override
+ public ObservableList getInternalMeetingList() {
+ return modelMeeting.getInternalMeetingList();
+ }
+
+ @Override
+ public Path getMeetingBookFilePath() {
+ return modelMeeting.getMeetingBookFilePath();
+ }
+
+ @Override
+ public GuiSettings getGuiSettings() {
+ return modelMeeting.getGuiSettings();
+ }
+
+ @Override
+ public void setGuiSettings(GuiSettings guiSettings) {
+ modelMeeting.setGuiSettings(guiSettings);
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/LogicPerson.java
similarity index 81%
rename from src/main/java/seedu/address/logic/Logic.java
rename to src/main/java/seedu/address/logic/LogicPerson.java
index 92cd8fa605a..7f6c46203b0 100644
--- a/src/main/java/seedu/address/logic/Logic.java
+++ b/src/main/java/seedu/address/logic/LogicPerson.java
@@ -7,13 +7,14 @@
import seedu.address.logic.commands.CommandResult;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Person;
+import seedu.address.model.person.ModelPerson;
+import seedu.address.model.person.ReadOnlyAddressBook;
+import seedu.address.model.person.person.Person;
/**
* API of the Logic component
*/
-public interface Logic {
+public interface LogicPerson {
/**
* Executes the command and returns the result.
* @param commandText The command as entered by the user.
@@ -26,13 +27,16 @@ public interface Logic {
/**
* Returns the AddressBook.
*
- * @see seedu.address.model.Model#getAddressBook()
+ * @see ModelPerson#getAddressBook()
*/
ReadOnlyAddressBook getAddressBook();
/** Returns an unmodifiable view of the filtered list of persons */
ObservableList getFilteredPersonList();
+ /** Returns the person currently in view. */
+ Person getPersonInView();
+
/**
* Returns the user prefs' address book file path.
*/
diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicPersonManager.java
similarity index 56%
rename from src/main/java/seedu/address/logic/LogicManager.java
rename to src/main/java/seedu/address/logic/LogicPersonManager.java
index 9d9c6d15bdc..0b52d8eb00c 100644
--- a/src/main/java/seedu/address/logic/LogicManager.java
+++ b/src/main/java/seedu/address/logic/LogicPersonManager.java
@@ -2,51 +2,47 @@
import java.io.IOException;
import java.nio.file.Path;
-import java.util.logging.Logger;
import javafx.collections.ObservableList;
import seedu.address.commons.core.GuiSettings;
-import seedu.address.commons.core.LogsCenter;
-import seedu.address.logic.commands.Command;
import seedu.address.logic.commands.CommandResult;
import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.logic.parser.AddressBookParser;
+import seedu.address.logic.commands.person.Command;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.Model;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Person;
-import seedu.address.storage.Storage;
+import seedu.address.logic.parser.person.AddressBookParser;
+import seedu.address.model.person.ModelPerson;
+import seedu.address.model.person.ReadOnlyAddressBook;
+import seedu.address.model.person.person.Person;
+import seedu.address.storage.person.StoragePerson;
/**
* The main LogicManager of the app.
*/
-public class LogicManager implements Logic {
+public class LogicPersonManager implements LogicPerson {
public static final String FILE_OPS_ERROR_MESSAGE = "Could not save data to file: ";
- private final Logger logger = LogsCenter.getLogger(LogicManager.class);
- private final Model model;
- private final Storage storage;
+ private final ModelPerson modelPerson;
+ private final StoragePerson storagePerson;
private final AddressBookParser addressBookParser;
/**
* Constructs a {@code LogicManager} with the given {@code Model} and {@code Storage}.
*/
- public LogicManager(Model model, Storage storage) {
- this.model = model;
- this.storage = storage;
+ public LogicPersonManager(ModelPerson modelPerson, StoragePerson storagePerson) {
+ this.modelPerson = modelPerson;
+ this.storagePerson = storagePerson;
addressBookParser = new AddressBookParser();
}
@Override
public CommandResult execute(String commandText) throws CommandException, ParseException {
- logger.info("----------------[USER COMMAND][" + commandText + "]");
CommandResult commandResult;
Command command = addressBookParser.parseCommand(commandText);
- commandResult = command.execute(model);
+ commandResult = command.execute(modelPerson);
try {
- storage.saveAddressBook(model.getAddressBook());
+ storagePerson.saveAddressBook(modelPerson.getAddressBook());
} catch (IOException ioe) {
throw new CommandException(FILE_OPS_ERROR_MESSAGE + ioe, ioe);
}
@@ -56,26 +52,31 @@ public CommandResult execute(String commandText) throws CommandException, ParseE
@Override
public ReadOnlyAddressBook getAddressBook() {
- return model.getAddressBook();
+ return modelPerson.getAddressBook();
}
@Override
public ObservableList getFilteredPersonList() {
- return model.getFilteredPersonList();
+ return modelPerson.getFilteredPersonList();
+ }
+
+ @Override
+ public Person getPersonInView() {
+ return modelPerson.getPersonInView();
}
@Override
public Path getAddressBookFilePath() {
- return model.getAddressBookFilePath();
+ return modelPerson.getAddressBookFilePath();
}
@Override
public GuiSettings getGuiSettings() {
- return model.getGuiSettings();
+ return modelPerson.getGuiSettings();
}
@Override
public void setGuiSettings(GuiSettings guiSettings) {
- model.setGuiSettings(guiSettings);
+ modelPerson.setGuiSettings(guiSettings);
}
}
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/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java
index 92f900b7916..960b770b558 100644
--- a/src/main/java/seedu/address/logic/commands/CommandResult.java
+++ b/src/main/java/seedu/address/logic/commands/CommandResult.java
@@ -4,6 +4,8 @@
import java.util.Objects;
+import seedu.address.commons.ModeEnum;
+
/**
* Represents the result of a command execution.
*/
@@ -17,13 +19,17 @@ public class CommandResult {
/** The application should exit. */
private final boolean exit;
+ private final ModeEnum mode;
+
+
/**
* Constructs a {@code CommandResult} with the specified fields.
*/
- public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) {
+ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit, ModeEnum mode) {
this.feedbackToUser = requireNonNull(feedbackToUser);
this.showHelp = showHelp;
this.exit = exit;
+ this.mode = mode;
}
/**
@@ -31,7 +37,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, null);
}
public String getFeedbackToUser() {
@@ -46,6 +52,11 @@ public boolean isExit() {
return exit;
}
+ public ModeEnum getMode() {
+ return mode;
+ }
+
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -60,12 +71,13 @@ public boolean equals(Object other) {
CommandResult otherCommandResult = (CommandResult) other;
return feedbackToUser.equals(otherCommandResult.feedbackToUser)
&& showHelp == otherCommandResult.showHelp
- && exit == otherCommandResult.exit;
+ && exit == otherCommandResult.exit
+ && mode == otherCommandResult.mode;
}
@Override
public int hashCode() {
- return Objects.hash(feedbackToUser, showHelp, exit);
+ return Objects.hash(feedbackToUser, showHelp, exit, mode);
}
}
diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java
deleted file mode 100644
index 84be6ad2596..00000000000
--- a/src/main/java/seedu/address/logic/commands/ListCommand.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
-
-import seedu.address.model.Model;
-
-/**
- * Lists all persons in the address book to the user.
- */
-public class ListCommand extends Command {
-
- public static final String COMMAND_WORD = "list";
-
- public static final String MESSAGE_SUCCESS = "Listed all persons";
-
-
- @Override
- public CommandResult execute(Model model) {
- requireNonNull(model);
- model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
- return new CommandResult(MESSAGE_SUCCESS);
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/deliverable/AddCommand.java b/src/main/java/seedu/address/logic/commands/deliverable/AddCommand.java
new file mode 100644
index 00000000000..3849c654e6e
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/deliverable/AddCommand.java
@@ -0,0 +1,68 @@
+package seedu.address.logic.commands.deliverable;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.deliverable.CliSyntax.PREFIX_CONTACTS;
+import static seedu.address.logic.parser.deliverable.CliSyntax.PREFIX_DEADLINE;
+import static seedu.address.logic.parser.deliverable.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.deliverable.CliSyntax.PREFIX_MILESTONE;
+import static seedu.address.logic.parser.deliverable.CliSyntax.PREFIX_TITLE;
+
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.deliverable.ModelDeliverable;
+import seedu.address.model.deliverable.deliverable.Deliverable;
+
+/**
+ * Adds a deliverable to the deliverable book.
+ */
+public class AddCommand extends Command {
+
+ public static final String COMMAND_WORD = "add";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a deliverable to the deliverable list.\n"
+ + "Parameters: "
+ + PREFIX_TITLE + "TITLE "
+ + PREFIX_DEADLINE + "DEADLINE "
+ + PREFIX_MILESTONE + "MILESTONE "
+ + "[" + PREFIX_CONTACTS + "CONTACTS] "
+ + "[" + PREFIX_DESCRIPTION + "DESCRIPTION]\n"
+ + "Example: " + COMMAND_WORD + " "
+ + PREFIX_TITLE + "Login screen "
+ + PREFIX_DEADLINE + "12-12-2020 23:59 "
+ + PREFIX_MILESTONE + "1.0 "
+ + PREFIX_CONTACTS + "John Martin, Abby Li "
+ + PREFIX_DESCRIPTION + "Must include username and password fields";
+
+ public static final String MESSAGE_SUCCESS = "Added new deliverable: %1$s";
+ public static final String MESSAGE_DUPLICATE_DELIVERABLE =
+ "This deliverable already exists in the deliverable list.";
+
+ private final Deliverable toAdd;
+
+ /**
+ * Creates an AddCommand to add the specified {@code Deliverable}
+ */
+ public AddCommand(Deliverable deliverable) {
+ requireNonNull(deliverable);
+ toAdd = deliverable;
+ }
+
+ @Override
+ public CommandResult execute(ModelDeliverable modelDeliverable) throws CommandException {
+ requireNonNull(modelDeliverable);
+
+ if (modelDeliverable.hasDeliverable(toAdd)) {
+ throw new CommandException(MESSAGE_DUPLICATE_DELIVERABLE);
+ }
+
+ modelDeliverable.addDeliverable(toAdd);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof AddCommand // instanceof handles nulls
+ && toAdd.equals(((AddCommand) other).toAdd));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/deliverable/ClearCommand.java b/src/main/java/seedu/address/logic/commands/deliverable/ClearCommand.java
new file mode 100644
index 00000000000..8e67dd8463c
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/deliverable/ClearCommand.java
@@ -0,0 +1,29 @@
+package seedu.address.logic.commands.deliverable;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.deliverable.DeliverableBook;
+import seedu.address.model.deliverable.ModelDeliverable;
+
+/**
+ * Clears the deliverable book.
+ */
+public class ClearCommand extends Command {
+ public static final String COMMAND_WORD = "clear";
+ public static final String MESSAGE_SUCCESS = "Cleared all deliverables!";
+
+ @Override
+ public CommandResult execute(ModelDeliverable modelDeliverable) throws CommandException {
+ requireNonNull(modelDeliverable);
+
+ if (modelDeliverable.getInternalDeliverableList().size() == 0) {
+ throw new CommandException(String.format(Messages.MESSAGE_INVALID_DELIVERABLE_LIST_EMPTY, COMMAND_WORD));
+ }
+
+ modelDeliverable.setDeliverableBook(new DeliverableBook());
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/deliverable/Command.java b/src/main/java/seedu/address/logic/commands/deliverable/Command.java
new file mode 100644
index 00000000000..35d96553f0e
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/deliverable/Command.java
@@ -0,0 +1,19 @@
+package seedu.address.logic.commands.deliverable;
+
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.deliverable.ModelDeliverable;
+
+/**
+ * Represents a command for deliverable with hidden internal logic and the ability to be executed.
+ */
+public abstract class Command {
+ /**
+ * Executes the command and returns the result message.
+ *
+ * @param modelDeliverable {@code ModelDeliverable} which the command should operate on.
+ * @return feedback message of the operation result for display
+ * @throws CommandException If an error occurs during command execution.
+ */
+ public abstract CommandResult execute(ModelDeliverable modelDeliverable) throws CommandException;
+}
diff --git a/src/main/java/seedu/address/logic/commands/deliverable/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/deliverable/DeleteCommand.java
new file mode 100644
index 00000000000..d23ed1ac593
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/deliverable/DeleteCommand.java
@@ -0,0 +1,54 @@
+package seedu.address.logic.commands.deliverable;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.deliverable.ModelDeliverable;
+import seedu.address.model.deliverable.deliverable.Deliverable;
+
+/**
+ * Deletes a deliverable identified using it's displayed index from the deliverable book.
+ */
+public class DeleteCommand extends Command {
+
+ public static final String COMMAND_WORD = "delete";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Deletes the deliverable identified by the index number used in the displayed deliverable list.\n"
+ + "Parameters: INDEX (must be a positive integer)\n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_DELETE_DELIVERABLE_SUCCESS = "Deleted deliverable: %1$s";
+
+ private final Index targetIndex;
+
+ public DeleteCommand(Index targetIndex) {
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(ModelDeliverable modelDeliverable) throws CommandException {
+ requireNonNull(modelDeliverable);
+ List lastShownList = modelDeliverable.getFilteredDeliverableList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_DELIVERABLE_DISPLAYED_INDEX);
+ }
+
+ Deliverable deliverableToDelete = lastShownList.get(targetIndex.getZeroBased());
+ modelDeliverable.deleteDeliverable(deliverableToDelete);
+ return new CommandResult(String.format(MESSAGE_DELETE_DELIVERABLE_SUCCESS, deliverableToDelete));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DeleteCommand // instanceof handles nulls
+ && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/deliverable/EditCommand.java b/src/main/java/seedu/address/logic/commands/deliverable/EditCommand.java
new file mode 100644
index 00000000000..6b0f4aac294
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/deliverable/EditCommand.java
@@ -0,0 +1,248 @@
+package seedu.address.logic.commands.deliverable;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_DELIVERABLE_DISPLAYED_INDEX;
+import static seedu.address.logic.parser.deliverable.CliSyntax.PREFIX_CONTACTS;
+import static seedu.address.logic.parser.deliverable.CliSyntax.PREFIX_DEADLINE;
+import static seedu.address.logic.parser.deliverable.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.deliverable.CliSyntax.PREFIX_MILESTONE;
+import static seedu.address.logic.parser.deliverable.CliSyntax.PREFIX_TITLE;
+
+import java.util.List;
+import java.util.Optional;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.util.CollectionUtil;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.deliverable.ModelDeliverable;
+import seedu.address.model.deliverable.deliverable.Deadline;
+import seedu.address.model.deliverable.deliverable.Deliverable;
+import seedu.address.model.deliverable.deliverable.Milestone;
+import seedu.address.model.util.Contacts;
+import seedu.address.model.util.Description;
+import seedu.address.model.util.Title;
+
+/**
+ * Edits the details of an existing deliverable.
+ */
+public class EditCommand extends Command {
+ public static final String COMMAND_WORD = "edit";
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the deliverable identified "
+ + "by the index number used in the displayed deliverable list. "
+ + "Existing values will be overwritten by the input values.\n"
+ + "Parameters: INDEX (must be a positive integer) "
+ + "[" + PREFIX_TITLE + "TITLE] "
+ + "[" + PREFIX_DEADLINE + "DEADLINE] "
+ + "[" + PREFIX_MILESTONE + "MILESTONE] "
+ + "[" + PREFIX_CONTACTS + "CONTACTS] "
+ + "[" + PREFIX_DESCRIPTION + "DESCRIPTION]\n"
+ + "Example: " + COMMAND_WORD + " 1 "
+ + PREFIX_TITLE + "Finalise beta release features "
+ + PREFIX_DEADLINE + "01-01-2020 12:00";
+
+
+ public static final String MESSAGE_EDIT_DELIVERABLE_SUCCESS = "Edited deliverable: %1$s";
+ public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
+ public static final String MESSAGE_DUPLICATE_DELIVERABLE = "This deliverable already"
+ + " exists in the deliverable list.";
+ public static final String MESSAGE_UNCHANGED = "Deliverable unchanged. At least one field must differ "
+ + "from the deliverable that is being edited.";
+
+
+ private final Index index;
+ private final EditDeliverableDescriptor editDeliverableDescriptor;
+
+ /**
+ * @param index of the deliverable in the filtered deliverable list to edit
+ * @param editDeliverableDescriptor details to edit the deliverable with
+ */
+ public EditCommand(Index index, EditDeliverableDescriptor editDeliverableDescriptor) {
+ requireNonNull(index);
+ requireNonNull(editDeliverableDescriptor);
+
+ this.index = index;
+ this.editDeliverableDescriptor = new EditDeliverableDescriptor(editDeliverableDescriptor);
+ }
+
+ @Override
+ public CommandResult execute(ModelDeliverable modelDeliverable) throws CommandException {
+ requireNonNull(modelDeliverable);
+
+ List lastShownList = modelDeliverable.getFilteredDeliverableList();
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(MESSAGE_INVALID_DELIVERABLE_DISPLAYED_INDEX);
+ }
+
+ Deliverable deliverableToEdit = lastShownList.get(index.getZeroBased());
+ Deliverable editedDeliverable = createEditedDeliverable(deliverableToEdit, editDeliverableDescriptor);
+
+ if (deliverableToEdit.equals(editedDeliverable)) {
+ throw new CommandException(MESSAGE_UNCHANGED);
+ }
+
+ if (!deliverableToEdit.isSameDeliverable(editedDeliverable) && modelDeliverable.hasDeliverable(
+ editedDeliverable)) {
+ throw new CommandException(MESSAGE_DUPLICATE_DELIVERABLE);
+ }
+
+ modelDeliverable.setDeliverable(deliverableToEdit, editedDeliverable);
+ return new CommandResult(String.format(MESSAGE_EDIT_DELIVERABLE_SUCCESS, editedDeliverable));
+ }
+
+ /**
+ * Creates and returns a {@code Deliverable} with the details of {@code deliverableToEdit}
+ * edited with {@code editDeliverableDescriptor}.
+ */
+ private static Deliverable createEditedDeliverable(
+ Deliverable deliverableToEdit, EditDeliverableDescriptor editDeliverableDescriptor)
+ throws IllegalArgumentException {
+ assert deliverableToEdit != null;
+
+ Title updatedTitle = editDeliverableDescriptor.getTitle().orElse(deliverableToEdit.getTitle());
+ Milestone updatedMilestone = editDeliverableDescriptor.getMilestone().orElse(deliverableToEdit.getMilestone());
+
+ // Description takes optional String
+ Description updatedDesc = editDeliverableDescriptor.getDescription()
+ .orElse(deliverableToEdit.getDescription());
+
+ Deadline updatedDeadline = editDeliverableDescriptor.getDeadline().orElse(deliverableToEdit.getDeadline());
+
+ boolean updatedIsComplete = deliverableToEdit.getIsComplete();
+
+ // Contacts takes optional String
+ Contacts updatedContacts = editDeliverableDescriptor.getContacts().orElse(deliverableToEdit.getContacts());
+
+ return new Deliverable(
+ updatedTitle, updatedMilestone, updatedDesc, updatedDeadline, updatedIsComplete, updatedContacts);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditCommand)) {
+ return false;
+ }
+
+ // state check
+ EditCommand e = (EditCommand) other;
+ return index.equals(e.index)
+ && editDeliverableDescriptor.equals(e.editDeliverableDescriptor);
+ }
+
+ /**
+ * Stores the details to edit the deliverable with. Each non-empty field value will replace the
+ * corresponding field value of the deliverable.
+ */
+ public static class EditDeliverableDescriptor {
+ private Title title;
+ private Milestone milestone;
+ private Description description;
+ private Deadline deadline;
+ private boolean isComplete;
+ private Contacts contacts;
+
+ public EditDeliverableDescriptor() {
+ }
+
+ /**
+ * Copy attributes from deliverable to be edited.
+ *
+ * @param toCopy deliverable to be edited.
+ */
+ public EditDeliverableDescriptor(EditDeliverableDescriptor toCopy) {
+ setTitle(toCopy.title);
+ setMilestone(toCopy.milestone);
+ setDescription(toCopy.description);
+ setDeadline(toCopy.deadline);
+ setIsComplete(toCopy.isComplete);
+ setContacts(toCopy.contacts);
+ }
+
+ /**
+ * Returns true if at least one field is edited.
+ */
+ public boolean isAnyFieldEdited() {
+ return CollectionUtil.isAnyNonNull(title, milestone, description, deadline, contacts);
+ }
+
+ public void setTitle(Title title) {
+ this.title = title;
+ }
+
+ public Optional getTitle() {
+ return Optional.ofNullable(this.title);
+ }
+
+ public void setMilestone(Milestone milestone) {
+ this.milestone = milestone;
+ }
+
+ public Optional getMilestone() {
+ return Optional.ofNullable(this.milestone);
+ }
+
+ public void setDescription(Description description) {
+ this.description = description;
+ }
+
+ public Optional getDescription() {
+ return Optional.ofNullable(description);
+ }
+
+ public void setDeadline(Deadline deadline) {
+ this.deadline = deadline;
+ }
+
+ public Optional getDeadline() {
+ return Optional.ofNullable(deadline);
+ }
+
+ public void setIsComplete(boolean isComplete) {
+ this.isComplete = isComplete;
+ }
+
+ public Optional getIsComplete() {
+ return Optional.ofNullable(this.isComplete);
+ }
+
+ public void setContacts(Contacts contact) {
+ this.contacts = contact;
+ }
+
+ public Optional getContacts() {
+ return Optional.ofNullable(this.contacts);
+ }
+
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditDeliverableDescriptor)) {
+ return false;
+ }
+
+ // state check
+ EditDeliverableDescriptor e = (EditDeliverableDescriptor) other;
+
+ return getTitle().equals(e.getTitle())
+ && getMilestone().equals(e.getMilestone())
+ && getDescription().equals(e.getDescription())
+ && getDeadline().equals(e.getDeadline())
+ && getIsComplete().equals(e.getIsComplete())
+ && getContacts().equals(e.getContacts());
+ }
+
+ }
+}
+
diff --git a/src/main/java/seedu/address/logic/commands/deliverable/FindCommand.java b/src/main/java/seedu/address/logic/commands/deliverable/FindCommand.java
new file mode 100644
index 00000000000..5c854f282a1
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/deliverable/FindCommand.java
@@ -0,0 +1,46 @@
+package seedu.address.logic.commands.deliverable;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.model.deliverable.ModelDeliverable;
+import seedu.address.model.deliverable.deliverable.TitleDescriptionContainsKeywordsPredicate;
+
+/**
+ * Finds and lists all deliverables in deliverable book whose title and/or description
+ * contains any of the argument keywords.
+ * Keyword matching is case insensitive.
+ */
+public class FindCommand extends Command {
+
+ public static final String COMMAND_WORD = "find";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all deliverables whose titles and/or "
+ + "descriptions contain any of "
+ + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n"
+ + "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
+ + "Example: " + COMMAND_WORD + " homepage navigation";
+
+ private final TitleDescriptionContainsKeywordsPredicate predicate;
+
+ public FindCommand(TitleDescriptionContainsKeywordsPredicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public CommandResult execute(ModelDeliverable modelDeliverable) {
+ requireNonNull(modelDeliverable);
+ modelDeliverable.updateFilteredDeliverableList(predicate);
+ return new CommandResult(
+ String.format(Messages.MESSAGE_DELIVERABLES_LISTED_OVERVIEW,
+ modelDeliverable.getFilteredDeliverableList().size()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof FindCommand // instanceof handles nulls
+ && predicate.equals(((FindCommand) other).predicate)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/deliverable/ListCommand.java b/src/main/java/seedu/address/logic/commands/deliverable/ListCommand.java
new file mode 100644
index 00000000000..a703d807620
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/deliverable/ListCommand.java
@@ -0,0 +1,30 @@
+package seedu.address.logic.commands.deliverable;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.deliverable.ModelDeliverable;
+
+/**
+ * Lists all deliverables in the deliverable book to the user.
+ */
+public class ListCommand extends Command {
+ public static final String COMMAND_WORD = "list";
+
+ public static final String MESSAGE_SUCCESS = "Listed all deliverables!";
+
+
+ @Override
+ public CommandResult execute(ModelDeliverable modelDeliverable) throws CommandException {
+ requireNonNull(modelDeliverable);
+
+ if (modelDeliverable.getInternalDeliverableList().size() == 0) {
+ throw new CommandException(String.format(Messages.MESSAGE_INVALID_DELIVERABLE_LIST_EMPTY, COMMAND_WORD));
+ }
+
+ modelDeliverable.updateFilteredDeliverableList(ModelDeliverable.PREDICATE_SHOW_ALL_DELIVERABLES);
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/deliverable/MarkDoneCommand.java b/src/main/java/seedu/address/logic/commands/deliverable/MarkDoneCommand.java
new file mode 100644
index 00000000000..7c2395bab8f
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/deliverable/MarkDoneCommand.java
@@ -0,0 +1,75 @@
+package seedu.address.logic.commands.deliverable;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.deliverable.ModelDeliverable;
+import seedu.address.model.deliverable.deliverable.Deadline;
+import seedu.address.model.deliverable.deliverable.Deliverable;
+import seedu.address.model.deliverable.deliverable.Milestone;
+import seedu.address.model.util.Contacts;
+import seedu.address.model.util.Description;
+import seedu.address.model.util.Title;
+
+/**
+ * Completes a deliverable
+ */
+public class MarkDoneCommand extends Command {
+
+ public static final String COMMAND_WORD = "done";
+ public static final String MESSAGE_DONE_DELIVERABLE_SUCCESS = "Marked deliverable as completed: %1$s";
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Marks as completed the deliverable identified by the index number "
+ + "used in the displayed deliverable list.\n"
+ + "Parameters: INDEX (must be a positive integer)\n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ private final Index targetIndex;
+
+ /**
+ * Construct command given index of deliverable to complete.
+ * @param targetIndex specified index of deliverable to complete.
+ */
+ public MarkDoneCommand(Index targetIndex) {
+ requireNonNull(targetIndex);
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(ModelDeliverable modelDeliverable) throws CommandException {
+ requireNonNull(modelDeliverable);
+ List lastShownList = modelDeliverable.getFilteredDeliverableList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_DELIVERABLE_DISPLAYED_INDEX);
+ }
+
+ Deliverable deliverableToComplete = lastShownList.get(targetIndex.getZeroBased());
+ Deliverable completedDeliverable = createCompletedDeliverable(deliverableToComplete);
+ modelDeliverable.updateDeliverableStatus(deliverableToComplete, completedDeliverable);
+ return new CommandResult(String.format(MESSAGE_DONE_DELIVERABLE_SUCCESS, deliverableToComplete));
+ }
+
+ private Deliverable createCompletedDeliverable(Deliverable deliverableToComplete) {
+ Title title = deliverableToComplete.getTitle();
+ Milestone milestone = deliverableToComplete.getMilestone();
+ Description description = deliverableToComplete.getDescription();
+ Deadline deadline = deliverableToComplete.getDeadline();
+ Contacts contacts = deliverableToComplete.getContacts();
+ return new Deliverable(title, milestone, description, deadline, true, contacts);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this
+ || other instanceof MarkDoneCommand
+ && targetIndex.equals(((MarkDoneCommand) other).targetIndex);
+ }
+
+
+}
diff --git a/src/main/java/seedu/address/logic/commands/deliverable/MarkUndoneCommand.java b/src/main/java/seedu/address/logic/commands/deliverable/MarkUndoneCommand.java
new file mode 100644
index 00000000000..ffbcd2e8344
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/deliverable/MarkUndoneCommand.java
@@ -0,0 +1,74 @@
+package seedu.address.logic.commands.deliverable;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.deliverable.ModelDeliverable;
+import seedu.address.model.deliverable.deliverable.Deadline;
+import seedu.address.model.deliverable.deliverable.Deliverable;
+import seedu.address.model.deliverable.deliverable.Milestone;
+import seedu.address.model.util.Contacts;
+import seedu.address.model.util.Description;
+import seedu.address.model.util.Title;
+
+/**
+ * Mark a deliverable as on-going.
+ */
+public class MarkUndoneCommand extends Command {
+
+ public static final String COMMAND_WORD = "undone";
+ public static final String MESSAGE_UNDONE_DELIVERABLE_SUCCESS = "Marked deliverable as on-going: %1$s";
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Marks as on-going the deliverable identified by the index number "
+ + "used in the displayed deliverable list.\n"
+ + "Parameters: INDEX (must be a positive integer)\n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ private final Index targetIndex;
+
+ /**
+ * Construct command given index of deliverable to mark as on-going.
+ * @param targetIndex specified index of deliverable to complete.
+ */
+ public MarkUndoneCommand(Index targetIndex) {
+ requireNonNull(targetIndex);
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(ModelDeliverable modelDeliverable) throws CommandException {
+ requireNonNull(modelDeliverable);
+ List lastShownList = modelDeliverable.getFilteredDeliverableList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_DELIVERABLE_DISPLAYED_INDEX);
+ }
+
+ Deliverable deliverableToMarkUndone = lastShownList.get(targetIndex.getZeroBased());
+
+ Deliverable unDoneDeliverable = createUndoneDeliverable(deliverableToMarkUndone);
+ modelDeliverable.updateDeliverableStatus(deliverableToMarkUndone, unDoneDeliverable);
+ return new CommandResult(String.format(MESSAGE_UNDONE_DELIVERABLE_SUCCESS, deliverableToMarkUndone));
+ }
+
+ private Deliverable createUndoneDeliverable(Deliverable deliverableToMarkUndone) {
+ Title title = deliverableToMarkUndone.getTitle();
+ Milestone milestone = deliverableToMarkUndone.getMilestone();
+ Description description = deliverableToMarkUndone.getDescription();
+ Deadline deadline = deliverableToMarkUndone.getDeadline();
+ Contacts contacts = deliverableToMarkUndone.getContacts();
+ return new Deliverable(title, milestone, description, deadline, false, contacts);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this
+ || other instanceof MarkUndoneCommand
+ && targetIndex.equals(((MarkUndoneCommand) other).targetIndex);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/deliverable/ViewCommand.java b/src/main/java/seedu/address/logic/commands/deliverable/ViewCommand.java
new file mode 100644
index 00000000000..4d767e636c5
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/deliverable/ViewCommand.java
@@ -0,0 +1,51 @@
+package seedu.address.logic.commands.deliverable;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.deliverable.ModelDeliverable;
+import seedu.address.model.deliverable.deliverable.Deliverable;
+
+/**
+ * View a deliverable by displaying its details in the side panel
+ */
+public class ViewCommand extends Command {
+ public static final String COMMAND_WORD = "view";
+ public static final String MESSAGE_VIEW_DELIVERABLE_SUCCESS = "Displayed deliverable: %1$s";
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Displays the details of the deliverable identified by the index number used in "
+ + "the displayed deliverable list.\n"
+ + "Parameters: INDEX (must be a positive integer)\n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ private final Index targetIndex;
+
+ /**
+ * Construct command given index of deliverable to view.
+ * @param targetIndex specified index of deliverable to view.
+ */
+ public ViewCommand(Index targetIndex) {
+ requireNonNull(targetIndex);
+ this.targetIndex = targetIndex;
+ }
+
+
+ @Override
+ public CommandResult execute(ModelDeliverable modelDeliverable) throws CommandException {
+ requireNonNull(modelDeliverable);
+ List lastShownList = modelDeliverable.getFilteredDeliverableList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_DELIVERABLE_DISPLAYED_INDEX);
+ }
+
+ Deliverable deliverableToView = lastShownList.get(targetIndex.getZeroBased());
+ modelDeliverable.setDeliverableInView(deliverableToView);
+ return new CommandResult(String.format(MESSAGE_VIEW_DELIVERABLE_SUCCESS, deliverableToView));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/general/Command.java b/src/main/java/seedu/address/logic/commands/general/Command.java
new file mode 100644
index 00000000000..8992508ed0e
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/general/Command.java
@@ -0,0 +1,17 @@
+package seedu.address.logic.commands.general;
+
+import seedu.address.logic.commands.CommandResult;
+
+/**
+ * Represents a command with hidden internal logic and the ability to be executed.
+ */
+public abstract class Command {
+
+ /**
+ * Executes the command and returns the result message.
+ *
+ * @return feedback message of the operation result for display
+ */
+ public abstract CommandResult execute();
+
+}
diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/general/ExitCommand.java
similarity index 60%
rename from src/main/java/seedu/address/logic/commands/ExitCommand.java
rename to src/main/java/seedu/address/logic/commands/general/ExitCommand.java
index 3dd85a8ba90..ac4ccdc348d 100644
--- a/src/main/java/seedu/address/logic/commands/ExitCommand.java
+++ b/src/main/java/seedu/address/logic/commands/general/ExitCommand.java
@@ -1,6 +1,6 @@
-package seedu.address.logic.commands;
+package seedu.address.logic.commands.general;
-import seedu.address.model.Model;
+import seedu.address.logic.commands.CommandResult;
/**
* Terminates the program.
@@ -9,11 +9,11 @@ public class ExitCommand extends Command {
public static final String COMMAND_WORD = "exit";
- public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ...";
+ public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Productiv as requested ...";
@Override
- public CommandResult execute(Model model) {
- return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true);
+ public CommandResult execute() {
+ return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true, null);
}
}
diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/general/HelpCommand.java
similarity index 74%
rename from src/main/java/seedu/address/logic/commands/HelpCommand.java
rename to src/main/java/seedu/address/logic/commands/general/HelpCommand.java
index bf824f91bd0..4ad134f7643 100644
--- a/src/main/java/seedu/address/logic/commands/HelpCommand.java
+++ b/src/main/java/seedu/address/logic/commands/general/HelpCommand.java
@@ -1,6 +1,6 @@
-package seedu.address.logic.commands;
+package seedu.address.logic.commands.general;
-import seedu.address.model.Model;
+import seedu.address.logic.commands.CommandResult;
/**
* Format full help instructions for every command for display.
@@ -12,10 +12,10 @@ public class HelpCommand extends Command {
public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n"
+ "Example: " + COMMAND_WORD;
- public static final String SHOWING_HELP_MESSAGE = "Opened help window.";
+ public static final String SHOWING_HELP_MESSAGE = "Opened help window!";
@Override
- public CommandResult execute(Model model) {
- return new CommandResult(SHOWING_HELP_MESSAGE, true, false);
+ public CommandResult execute() {
+ return new CommandResult(SHOWING_HELP_MESSAGE, true, false, null);
}
}
diff --git a/src/main/java/seedu/address/logic/commands/general/SwitchCommand.java b/src/main/java/seedu/address/logic/commands/general/SwitchCommand.java
new file mode 100644
index 00000000000..a0c320ecfdc
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/general/SwitchCommand.java
@@ -0,0 +1,44 @@
+package seedu.address.logic.commands.general;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.commons.ModeEnum;
+import seedu.address.logic.commands.CommandResult;
+
+/**
+ * Switches the mode of the application.
+ */
+public class SwitchCommand extends Command {
+
+ public static final String COMMAND_WORD = "switch";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Switches mode.\n"
+ + "Parameters: MODE (must be " + ModeEnum.getModeOptions()
+ + ")\n"
+ + "Example: " + COMMAND_WORD + " " + ModeEnum.DELIVERABLE.getArgument();
+
+
+ public static final String MESSAGE_SUCCESS = "Switched to: %1$s";
+
+ private final ModeEnum mode;
+
+ /**
+ * Creates a SwitchCommand to switch the specified {@code ModeEnum}
+ */
+ public SwitchCommand(ModeEnum mode) {
+ requireNonNull(mode);
+ this.mode = mode;
+ }
+
+ @Override
+ public CommandResult execute() {
+ return new CommandResult(String.format(MESSAGE_SUCCESS, mode), false, false, mode);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof SwitchCommand // instanceof handles nulls
+ && mode.equals(((SwitchCommand) other).mode)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/meeting/AddCommand.java b/src/main/java/seedu/address/logic/commands/meeting/AddCommand.java
new file mode 100644
index 00000000000..ce24bf7d47c
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/meeting/AddCommand.java
@@ -0,0 +1,66 @@
+package seedu.address.logic.commands.meeting;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.meeting.CliSyntax.PREFIX_CONTACTS;
+import static seedu.address.logic.parser.meeting.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.meeting.CliSyntax.PREFIX_FROM;
+import static seedu.address.logic.parser.meeting.CliSyntax.PREFIX_LOCATION;
+import static seedu.address.logic.parser.meeting.CliSyntax.PREFIX_TITLE;
+import static seedu.address.logic.parser.meeting.CliSyntax.PREFIX_TO;
+
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.meeting.ModelMeeting;
+import seedu.address.model.meeting.meeting.Meeting;
+
+public class AddCommand extends Command {
+ public static final String COMMAND_WORD = "add";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a meeting to the meeting list.\n"
+ + "Parameters: "
+ + PREFIX_TITLE + "TITLE "
+ + PREFIX_FROM + "FROM "
+ + PREFIX_TO + "TO "
+ + "[" + PREFIX_CONTACTS + "CONTACTS] "
+ + "[" + PREFIX_LOCATION + "LOCATION] "
+ + "[" + PREFIX_DESCRIPTION + "DESCRIPTION]\n"
+ + "Example: " + COMMAND_WORD + " "
+ + PREFIX_TITLE + "Discuss user requirements with biz associates "
+ + PREFIX_FROM + "30-12-2020 12:00 "
+ + PREFIX_TO + "14:00 "
+ + PREFIX_CONTACTS + "Abby Li, John Martin "
+ + PREFIX_LOCATION + "Room 1A "
+ + PREFIX_DESCRIPTION + "Refer to the reviewed user stories during discussion";
+
+ public static final String MESSAGE_SUCCESS = "Added new meeting: %1$s";
+ public static final String MESSAGE_DUPLICATE_MEETING = "This meeting already exists in the meeting list.";
+
+ private final Meeting toAdd;
+
+ /**
+ * Creates a MeetingCommand to add the specified {@code Meeting}
+ */
+ public AddCommand(Meeting meeting) {
+ requireNonNull(meeting);
+ toAdd = meeting;
+ }
+
+ @Override
+ public CommandResult execute(ModelMeeting modelMeeting) throws CommandException {
+ requireNonNull(modelMeeting);
+
+ if (modelMeeting.hasMeeting(toAdd)) {
+ throw new CommandException(MESSAGE_DUPLICATE_MEETING);
+ }
+
+ modelMeeting.addMeeting(toAdd);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this
+ || (other instanceof AddCommand
+ && toAdd.equals(((AddCommand) other).toAdd));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/meeting/ClearCommand.java b/src/main/java/seedu/address/logic/commands/meeting/ClearCommand.java
new file mode 100644
index 00000000000..7be69dc2e48
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/meeting/ClearCommand.java
@@ -0,0 +1,29 @@
+package seedu.address.logic.commands.meeting;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.meeting.MeetingBook;
+import seedu.address.model.meeting.ModelMeeting;
+
+/**
+ * Clears the meeting book.
+ */
+public class ClearCommand extends Command {
+ public static final String COMMAND_WORD = "clear";
+ public static final String MESSAGE_SUCCESS = "Cleared all meetings!";
+
+ @Override
+ public CommandResult execute(ModelMeeting modelMeeting) throws CommandException {
+ requireNonNull(modelMeeting);
+
+ if (modelMeeting.getInternalMeetingList().size() == 0) {
+ throw new CommandException(String.format(Messages.MESSAGE_INVALID_MEETING_LIST_EMPTY, COMMAND_WORD));
+ }
+
+ modelMeeting.setMeetingBook(new MeetingBook());
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/meeting/Command.java b/src/main/java/seedu/address/logic/commands/meeting/Command.java
new file mode 100644
index 00000000000..7b6d6f5d657
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/meeting/Command.java
@@ -0,0 +1,21 @@
+package seedu.address.logic.commands.meeting;
+
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.meeting.ModelMeeting;
+
+/**
+ * Represents a meeting command with hidden internal logic and the ability to be executed.
+ */
+public abstract class Command {
+ public static final String COMMAND_WORD = "meeting";
+
+ /**
+ * Executes the command and returns the result message.
+ *
+ * @param modelMeeting {@code Model} which the command should operate on.
+ * @return feedback message of the operation result for display
+ * @throws CommandException If an error occurs during command execution.
+ */
+ public abstract CommandResult execute(ModelMeeting modelMeeting) throws CommandException;
+}
diff --git a/src/main/java/seedu/address/logic/commands/meeting/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/meeting/DeleteCommand.java
new file mode 100644
index 00000000000..a0843e1cb52
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/meeting/DeleteCommand.java
@@ -0,0 +1,58 @@
+package seedu.address.logic.commands.meeting;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.meeting.ModelMeeting;
+import seedu.address.model.meeting.meeting.Meeting;
+
+/**
+ * Deletes a meeting given the specified index.
+ */
+public class DeleteCommand extends Command {
+
+ public static final String COMMAND_WORD = "delete";
+ public static final String MESSAGE_DELETE_MEETING_SUCCESS = "Deleted meeting: %1$s";
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Deletes the meeting identified by the index number used in the displayed meeting list.\n"
+ + "Parameters: INDEX (must be a positive integer)\n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ private final Index targetIndex;
+
+ /**
+ * Construct command given index of meeting to delete.
+ *
+ * @param targetIndex specified index of meeting to delete.
+ */
+ public DeleteCommand(Index targetIndex) {
+ requireNonNull(targetIndex);
+ this.targetIndex = targetIndex;
+ }
+
+
+ @Override
+ public CommandResult execute(ModelMeeting modelMeeting) throws CommandException {
+ requireNonNull(modelMeeting);
+ List lastShownList = modelMeeting.getFilteredMeetingList();
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_MEETING_DISPLAYED_INDEX);
+ }
+
+ Meeting meetingToDelete = lastShownList.get(targetIndex.getZeroBased());
+ modelMeeting.deleteMeeting(meetingToDelete);
+ return new CommandResult(String.format(MESSAGE_DELETE_MEETING_SUCCESS, meetingToDelete));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DeleteCommand) // instanceof handles nulls
+ && targetIndex.equals(((DeleteCommand) other).targetIndex);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/meeting/EditCommand.java b/src/main/java/seedu/address/logic/commands/meeting/EditCommand.java
new file mode 100644
index 00000000000..16b4f6907bb
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/meeting/EditCommand.java
@@ -0,0 +1,247 @@
+package seedu.address.logic.commands.meeting;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_MEETING_DISPLAYED_INDEX;
+import static seedu.address.logic.parser.meeting.CliSyntax.PREFIX_CONTACTS;
+import static seedu.address.logic.parser.meeting.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.meeting.CliSyntax.PREFIX_FROM;
+import static seedu.address.logic.parser.meeting.CliSyntax.PREFIX_LOCATION;
+import static seedu.address.logic.parser.meeting.CliSyntax.PREFIX_TITLE;
+import static seedu.address.logic.parser.meeting.CliSyntax.PREFIX_TO;
+
+import java.util.List;
+import java.util.Optional;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.util.CollectionUtil;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.meeting.ModelMeeting;
+import seedu.address.model.meeting.meeting.From;
+import seedu.address.model.meeting.meeting.Location;
+import seedu.address.model.meeting.meeting.Meeting;
+import seedu.address.model.meeting.meeting.To;
+import seedu.address.model.util.Contacts;
+import seedu.address.model.util.Description;
+import seedu.address.model.util.Title;
+
+/**
+ * Edits the details of an existing meeting.
+ */
+public class EditCommand extends Command {
+
+ public static final String COMMAND_WORD = "edit";
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the meeting identified "
+ + "by the index number used in the displayed meeting list. "
+ + "Existing values will be overwritten by the input values.\n"
+ + "Parameters: INDEX (must be a positive integer) "
+ + "[" + PREFIX_TITLE + "TITLE] "
+ + "[" + PREFIX_FROM + "FROM] "
+ + "[" + PREFIX_TO + "TO] "
+ + "[" + PREFIX_CONTACTS + "CONTACTS] "
+ + "[" + PREFIX_LOCATION + "LOCATION] "
+ + "[" + PREFIX_DESCRIPTION + "DESCRIPTION]\n"
+ + "Example: " + COMMAND_WORD + " 1 "
+ + PREFIX_TITLE + "Discuss ALL features "
+ + PREFIX_FROM + "12-10-2020 09:00";
+
+
+ public static final String MESSAGE_EDIT_MEETING_SUCCESS = "Edited meeting: %1$s";
+ public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
+ public static final String MESSAGE_DUPLICATE_MEETING = "This meeting already exists in the meeting list.";
+ public static final String MESSAGE_UNCHANGED = "Meeting unchanged. At least one field must differ "
+ + "from the meeting that is being edited.";
+
+ private final Index targetIndex;
+ private final EditMeetingDescriptor editMeetingDescriptor;
+
+ /**
+ * @param index of the meeting in the filtered meeting list to edit
+ * @param editMeetingDescriptor details to edit the meeting with
+ */
+ public EditCommand(Index index, EditMeetingDescriptor editMeetingDescriptor) {
+ requireNonNull(index);
+ requireNonNull(editMeetingDescriptor);
+
+ this.targetIndex = index;
+ this.editMeetingDescriptor = new EditMeetingDescriptor(editMeetingDescriptor);
+ }
+
+ @Override
+ public CommandResult execute(ModelMeeting modelMeeting) throws CommandException {
+ requireNonNull(modelMeeting);
+
+ List lastShownList = modelMeeting.getFilteredMeetingList();
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(MESSAGE_INVALID_MEETING_DISPLAYED_INDEX);
+ }
+
+ Meeting meetingToEdit = lastShownList.get(targetIndex.getZeroBased());
+ Meeting editedMeeting;
+
+ try {
+ editedMeeting = createEditedMeeting(meetingToEdit, editMeetingDescriptor);
+ } catch (IllegalArgumentException e) {
+ throw new CommandException(e.getMessage());
+ }
+
+ assert Meeting.isValidFromAndTo(editedMeeting.getFrom(), editedMeeting.getTo())
+ : "From should be earlier than To";
+
+ if (meetingToEdit.equals(editedMeeting)) {
+ throw new CommandException(MESSAGE_UNCHANGED);
+ }
+
+ if (!meetingToEdit.isSameMeeting(editedMeeting) && modelMeeting.hasMeeting(editedMeeting)) {
+ throw new CommandException(MESSAGE_DUPLICATE_MEETING);
+ }
+
+ modelMeeting.setMeeting(meetingToEdit, editedMeeting);
+ return new CommandResult(String.format(MESSAGE_EDIT_MEETING_SUCCESS, editedMeeting));
+ }
+
+ private static Meeting createEditedMeeting(Meeting meetingToEdit, EditMeetingDescriptor editMeetingDescriptor)
+ throws IllegalArgumentException {
+ assert meetingToEdit != null;
+
+ Title updatedTitle = editMeetingDescriptor.getTitle().orElse(meetingToEdit.getTitle());
+
+ // Description takes optional String
+ Description updatedDesc = editMeetingDescriptor.getDescription()
+ .orElse(meetingToEdit.getDescription());
+
+ From updatedFrom = editMeetingDescriptor.getFrom().orElse(meetingToEdit.getFrom());
+ To updatedTo = editMeetingDescriptor.getTo().orElse(meetingToEdit.getTo());
+
+ // Contacts takes optional String
+ Contacts updatedContacts = editMeetingDescriptor.getContacts().orElse(meetingToEdit.getContacts());
+
+ // Location takes optional String
+ Location updatedLocation = editMeetingDescriptor.getLocation().orElse(meetingToEdit.getLocation());
+
+ return new Meeting(updatedTitle, updatedDesc, updatedFrom, updatedTo, updatedContacts, updatedLocation);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditCommand)) {
+ return false;
+ }
+
+ // state check
+ EditCommand e = (EditCommand) other;
+ return targetIndex.equals(e.targetIndex)
+ && editMeetingDescriptor.equals(e.editMeetingDescriptor);
+ }
+
+ /**
+ *
+ */
+ public static class EditMeetingDescriptor {
+ private Title title;
+ private Description description;
+ private From from;
+ private To to;
+ private Contacts contacts;
+ private Location location;
+
+ public EditMeetingDescriptor() {
+ }
+
+ /**
+ * Copy attributes from meeting to be edited.
+ *
+ * @param toCopy meeting to be edited.
+ */
+ public EditMeetingDescriptor(EditMeetingDescriptor toCopy) {
+ setTitle(toCopy.title);
+ setDescription(toCopy.description);
+ setFrom(toCopy.from);
+ setTo(toCopy.to);
+ setContacts(toCopy.contacts);
+ setLocation(toCopy.location);
+ }
+
+ public boolean isAnyFieldEdited() {
+ return CollectionUtil.isAnyNonNull(title, description, from, to, contacts, location);
+ }
+
+ public void setTitle(Title title) {
+ this.title = title;
+ }
+
+ public Optional getTitle() {
+ return Optional.ofNullable(this.title);
+ }
+
+ public void setDescription(Description description) {
+ this.description = description;
+ }
+
+ public Optional getDescription() {
+ return Optional.ofNullable(description);
+ }
+
+ public void setFrom(From from) {
+ this.from = from;
+ }
+
+ public Optional getFrom() {
+ return Optional.ofNullable(this.from);
+ }
+
+ public void setTo(To to) {
+ this.to = to;
+ }
+
+ public Optional getTo() {
+ return Optional.ofNullable(this.to);
+ }
+
+ public void setContacts(Contacts contact) {
+ this.contacts = contact;
+ }
+
+ public Optional getContacts() {
+ return Optional.ofNullable(this.contacts);
+ }
+
+ public void setLocation(Location location) {
+ this.location = location;
+ }
+
+ public Optional getLocation() {
+ return Optional.ofNullable(this.location);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ // short circuit if same object
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditMeetingDescriptor)) {
+ return false;
+ }
+
+ // state check
+ EditMeetingDescriptor e = (EditMeetingDescriptor) other;
+
+ return getTitle().equals(e.getTitle())
+ && getDescription().equals(e.getDescription())
+ && getFrom().equals(e.getFrom())
+ && getTo().equals(e.getTo())
+ && getContacts().equals(e.getContacts())
+ && getLocation().equals(e.getLocation());
+ }
+
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/meeting/FindCommand.java b/src/main/java/seedu/address/logic/commands/meeting/FindCommand.java
new file mode 100644
index 00000000000..5e777fccb12
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/meeting/FindCommand.java
@@ -0,0 +1,44 @@
+package seedu.address.logic.commands.meeting;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_MEETINGS_LISTED_OVERVIEW;
+
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.model.meeting.ModelMeeting;
+import seedu.address.model.meeting.meeting.TitleDescriptionContainsKeywordsPredicate;
+
+/**
+ * Finds and lists all meeting in address book whose name contains any of the argument keywords.
+ * Keyword matching is case insensitive.
+ */
+public class FindCommand extends Command {
+
+ public static final String COMMAND_WORD = "find";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all meetings whose titles and/or "
+ + "descriptions contain any of "
+ + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n"
+ + "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
+ + "Example: " + COMMAND_WORD + " discuss user guide";
+
+ private final TitleDescriptionContainsKeywordsPredicate predicate;
+
+ public FindCommand(TitleDescriptionContainsKeywordsPredicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public CommandResult execute(ModelMeeting modelMeeting) {
+ requireNonNull(modelMeeting);
+ modelMeeting.updateFilteredMeetingList(predicate);
+ return new CommandResult(
+ String.format(MESSAGE_MEETINGS_LISTED_OVERVIEW, modelMeeting.getFilteredMeetingList().size()));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof FindCommand // instanceof handles nulls
+ && predicate.equals(((FindCommand) other).predicate)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/meeting/ListCommand.java b/src/main/java/seedu/address/logic/commands/meeting/ListCommand.java
new file mode 100644
index 00000000000..de83333a674
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/meeting/ListCommand.java
@@ -0,0 +1,28 @@
+package seedu.address.logic.commands.meeting;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.model.meeting.ModelMeeting.PREDICATE_SHOW_ALL_MEETINGS;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.meeting.ModelMeeting;
+
+public class ListCommand extends Command {
+
+ public static final String COMMAND_WORD = "list";
+
+ public static final String MESSAGE_SUCCESS = "Listed all meetings!";
+
+ @Override
+ public CommandResult execute(ModelMeeting modelMeeting) throws CommandException {
+ requireNonNull(modelMeeting);
+
+ if (modelMeeting.getInternalMeetingList().size() == 0) {
+ throw new CommandException(String.format(Messages.MESSAGE_INVALID_MEETING_LIST_EMPTY, COMMAND_WORD));
+ }
+
+ modelMeeting.updateFilteredMeetingList(PREDICATE_SHOW_ALL_MEETINGS);
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/meeting/ViewCommand.java b/src/main/java/seedu/address/logic/commands/meeting/ViewCommand.java
new file mode 100644
index 00000000000..052cfa0b8ab
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/meeting/ViewCommand.java
@@ -0,0 +1,50 @@
+package seedu.address.logic.commands.meeting;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.meeting.ModelMeeting;
+import seedu.address.model.meeting.meeting.Meeting;
+
+/**
+ * View a meeting by displaying its details in the side panel
+ */
+public class ViewCommand extends Command {
+ public static final String COMMAND_WORD = "view";
+ public static final String MESSAGE_VIEW_MEETING_SUCCESS = "Displayed meeting: %1$s";
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Displays the details of the meeting identified by the index number used in the displayed meeting list.\n"
+ + "Parameters: INDEX (must be a positive integer)\n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ private final Index targetIndex;
+
+ /**
+ * Construct command given index of meeting to view.
+ * @param targetIndex specified index of meeting to view.
+ */
+ public ViewCommand(Index targetIndex) {
+ requireNonNull(targetIndex);
+ this.targetIndex = targetIndex;
+ }
+
+
+ @Override
+ public CommandResult execute(ModelMeeting modelMeeting) throws CommandException {
+ requireNonNull(modelMeeting);
+ List lastShownList = modelMeeting.getFilteredMeetingList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_MEETING_DISPLAYED_INDEX);
+ }
+
+ Meeting meetingToView = lastShownList.get(targetIndex.getZeroBased());
+ modelMeeting.setMeetingInView(meetingToView);
+ return new CommandResult(String.format(MESSAGE_VIEW_MEETING_SUCCESS, meetingToView));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/person/AddCommand.java
similarity index 53%
rename from src/main/java/seedu/address/logic/commands/AddCommand.java
rename to src/main/java/seedu/address/logic/commands/person/AddCommand.java
index 71656d7c5c8..3330058ba6f 100644
--- a/src/main/java/seedu/address/logic/commands/AddCommand.java
+++ b/src/main/java/seedu/address/logic/commands/person/AddCommand.java
@@ -1,15 +1,16 @@
-package seedu.address.logic.commands;
+package seedu.address.logic.commands.person;
import static java.util.Objects.requireNonNull;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.person.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.person.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.person.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.person.CliSyntax.PREFIX_PHONE;
+import static seedu.address.logic.parser.person.CliSyntax.PREFIX_ROLE;
+import seedu.address.logic.commands.CommandResult;
import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
-import seedu.address.model.person.Person;
+import seedu.address.model.person.ModelPerson;
+import seedu.address.model.person.person.Person;
/**
* Adds a person to the address book.
@@ -18,23 +19,22 @@ public class AddCommand extends Command {
public static final String COMMAND_WORD = "add";
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. "
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a contact to the contact list.\n"
+ "Parameters: "
+ PREFIX_NAME + "NAME "
- + PREFIX_PHONE + "PHONE "
+ + PREFIX_ROLE + "ROLE "
+ PREFIX_EMAIL + "EMAIL "
- + PREFIX_ADDRESS + "ADDRESS "
- + "[" + PREFIX_TAG + "TAG]...\n"
+ + "[" + PREFIX_PHONE + "PHONE] "
+ + "[" + PREFIX_DESCRIPTION + "DESCRIPTION]\n"
+ "Example: " + COMMAND_WORD + " "
+ PREFIX_NAME + "John Doe "
- + PREFIX_PHONE + "98765432 "
+ + PREFIX_ROLE + "stk "
+ PREFIX_EMAIL + "johnd@example.com "
- + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 "
- + PREFIX_TAG + "friends "
- + PREFIX_TAG + "owesMoney";
+ + PREFIX_PHONE + "98765432 "
+ + PREFIX_DESCRIPTION + "End user";
- public static final String MESSAGE_SUCCESS = "New person added: %1$s";
- public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book";
+ public static final String MESSAGE_SUCCESS = "Added contact: %1$s";
+ public static final String MESSAGE_DUPLICATE_PERSON = "This contact already exists in the contact list.";
private final Person toAdd;
@@ -47,14 +47,14 @@ public AddCommand(Person person) {
}
@Override
- public CommandResult execute(Model model) throws CommandException {
- requireNonNull(model);
+ public CommandResult execute(ModelPerson modelPerson) throws CommandException {
+ requireNonNull(modelPerson);
- if (model.hasPerson(toAdd)) {
+ if (modelPerson.hasPerson(toAdd)) {
throw new CommandException(MESSAGE_DUPLICATE_PERSON);
}
- model.addPerson(toAdd);
+ modelPerson.addPerson(toAdd);
return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd));
}
diff --git a/src/main/java/seedu/address/logic/commands/person/ClearCommand.java b/src/main/java/seedu/address/logic/commands/person/ClearCommand.java
new file mode 100644
index 00000000000..478378892b1
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/person/ClearCommand.java
@@ -0,0 +1,31 @@
+package seedu.address.logic.commands.person;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.person.AddressBook;
+import seedu.address.model.person.ModelPerson;
+
+/**
+ * Clears the address book.
+ */
+public class ClearCommand extends Command {
+
+ public static final String COMMAND_WORD = "clear";
+ public static final String MESSAGE_SUCCESS = "Cleared all contacts!";
+
+
+ @Override
+ public CommandResult execute(ModelPerson modelPerson) throws CommandException {
+ requireNonNull(modelPerson);
+
+ if (modelPerson.getInternalPersonList().size() == 0) {
+ throw new CommandException(String.format(Messages.MESSAGE_INVALID_CONTACT_LIST_EMPTY, COMMAND_WORD));
+ }
+
+ modelPerson.setAddressBook(new AddressBook());
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/address/logic/commands/person/Command.java
similarity index 57%
rename from src/main/java/seedu/address/logic/commands/Command.java
rename to src/main/java/seedu/address/logic/commands/person/Command.java
index 64f18992160..6a2b8d0b8f8 100644
--- a/src/main/java/seedu/address/logic/commands/Command.java
+++ b/src/main/java/seedu/address/logic/commands/person/Command.java
@@ -1,7 +1,8 @@
-package seedu.address.logic.commands;
+package seedu.address.logic.commands.person;
+import seedu.address.logic.commands.CommandResult;
import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
+import seedu.address.model.person.ModelPerson;
/**
* Represents a command with hidden internal logic and the ability to be executed.
@@ -11,10 +12,10 @@ public abstract class Command {
/**
* Executes the command and returns the result message.
*
- * @param model {@code Model} which the command should operate on.
+ * @param modelPerson {@code Model} which the command should operate on.
* @return feedback message of the operation result for display
* @throws CommandException If an error occurs during command execution.
*/
- public abstract CommandResult execute(Model model) throws CommandException;
+ public abstract CommandResult execute(ModelPerson modelPerson) throws CommandException;
}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/person/DeleteCommand.java
similarity index 70%
rename from src/main/java/seedu/address/logic/commands/DeleteCommand.java
rename to src/main/java/seedu/address/logic/commands/person/DeleteCommand.java
index 02fd256acba..524e396265b 100644
--- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java
+++ b/src/main/java/seedu/address/logic/commands/person/DeleteCommand.java
@@ -1,4 +1,4 @@
-package seedu.address.logic.commands;
+package seedu.address.logic.commands.person;
import static java.util.Objects.requireNonNull;
@@ -6,9 +6,10 @@
import seedu.address.commons.core.Messages;
import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.CommandResult;
import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
-import seedu.address.model.person.Person;
+import seedu.address.model.person.ModelPerson;
+import seedu.address.model.person.person.Person;
/**
* Deletes a person identified using it's displayed index from the address book.
@@ -18,11 +19,11 @@ public class DeleteCommand extends Command {
public static final String COMMAND_WORD = "delete";
public static final String MESSAGE_USAGE = COMMAND_WORD
- + ": Deletes the person identified by the index number used in the displayed person list.\n"
+ + ": Deletes the contact identified by the index number used in the displayed contact list.\n"
+ "Parameters: INDEX (must be a positive integer)\n"
+ "Example: " + COMMAND_WORD + " 1";
- public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s";
+ public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted contact: %1$s";
private final Index targetIndex;
@@ -31,16 +32,16 @@ public DeleteCommand(Index targetIndex) {
}
@Override
- public CommandResult execute(Model model) throws CommandException {
- requireNonNull(model);
- List lastShownList = model.getFilteredPersonList();
+ public CommandResult execute(ModelPerson modelPerson) throws CommandException {
+ requireNonNull(modelPerson);
+ List lastShownList = modelPerson.getFilteredPersonList();
if (targetIndex.getZeroBased() >= lastShownList.size()) {
- throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ throw new CommandException(Messages.MESSAGE_INVALID_CONTACT_DISPLAYED_INDEX);
}
Person personToDelete = lastShownList.get(targetIndex.getZeroBased());
- model.deletePerson(personToDelete);
+ modelPerson.deletePerson(personToDelete);
return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete));
}
diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/person/EditCommand.java
similarity index 64%
rename from src/main/java/seedu/address/logic/commands/EditCommand.java
rename to src/main/java/seedu/address/logic/commands/person/EditCommand.java
index 7e36114902f..d371cdb66dd 100644
--- a/src/main/java/seedu/address/logic/commands/EditCommand.java
+++ b/src/main/java/seedu/address/logic/commands/person/EditCommand.java
@@ -1,30 +1,27 @@
-package seedu.address.logic.commands;
+package seedu.address.logic.commands.person;
import static java.util.Objects.requireNonNull;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
-
-import java.util.Collections;
-import java.util.HashSet;
+import static seedu.address.logic.parser.person.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.person.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.person.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.person.CliSyntax.PREFIX_PHONE;
+import static seedu.address.logic.parser.person.CliSyntax.PREFIX_ROLE;
+
import java.util.List;
import java.util.Optional;
-import java.util.Set;
import seedu.address.commons.core.Messages;
import seedu.address.commons.core.index.Index;
import seedu.address.commons.util.CollectionUtil;
+import seedu.address.logic.commands.CommandResult;
import seedu.address.logic.commands.exceptions.CommandException;
-import seedu.address.model.Model;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
+import seedu.address.model.person.ModelPerson;
+import seedu.address.model.person.person.Email;
+import seedu.address.model.person.person.Name;
+import seedu.address.model.person.person.Person;
+import seedu.address.model.person.person.Phone;
+import seedu.address.model.person.person.Role;
+import seedu.address.model.util.Description;
/**
* Edits the details of an existing person in the address book.
@@ -32,23 +29,24 @@
public class EditCommand extends Command {
public static final String COMMAND_WORD = "edit";
-
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified "
- + "by the index number used in the displayed person list. "
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the contact identified "
+ + "by the index number used in the displayed contact list. "
+ "Existing values will be overwritten by the input values.\n"
+ "Parameters: INDEX (must be a positive integer) "
+ "[" + PREFIX_NAME + "NAME] "
- + "[" + PREFIX_PHONE + "PHONE] "
+ + "[" + PREFIX_ROLE + "ROLE] "
+ "[" + PREFIX_EMAIL + "EMAIL] "
- + "[" + PREFIX_ADDRESS + "ADDRESS] "
- + "[" + PREFIX_TAG + "TAG]...\n"
+ + "[" + PREFIX_PHONE + "PHONE] "
+ + "[" + PREFIX_DESCRIPTION + "DESCRIPTION]\n"
+ "Example: " + COMMAND_WORD + " 1 "
- + PREFIX_PHONE + "91234567 "
- + PREFIX_EMAIL + "johndoe@example.com";
+ + PREFIX_EMAIL + "johndoe@example.com "
+ + PREFIX_PHONE + "91234567";
- public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s";
+ public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited contact: %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 contact already exists in the contact list.";
+ public static final String MESSAGE_UNCHANGED = "Contact unchanged. At least one field must differ "
+ + "from the contact that is being edited.";
private final Index index;
private final EditPersonDescriptor editPersonDescriptor;
@@ -66,23 +64,26 @@ public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) {
}
@Override
- public CommandResult execute(Model model) throws CommandException {
- requireNonNull(model);
- List lastShownList = model.getFilteredPersonList();
+ public CommandResult execute(ModelPerson modelPerson) throws CommandException {
+ requireNonNull(modelPerson);
+ List lastShownList = modelPerson.getFilteredPersonList();
if (index.getZeroBased() >= lastShownList.size()) {
- throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ throw new CommandException(Messages.MESSAGE_INVALID_CONTACT_DISPLAYED_INDEX);
}
Person personToEdit = lastShownList.get(index.getZeroBased());
Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor);
- if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) {
+ if (personToEdit.equals(editedPerson)) {
+ throw new CommandException(MESSAGE_UNCHANGED);
+ }
+
+ if (!personToEdit.isSamePerson(editedPerson) && modelPerson.hasPerson(editedPerson)) {
throw new CommandException(MESSAGE_DUPLICATE_PERSON);
}
- model.setPerson(personToEdit, editedPerson);
- model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ modelPerson.setPerson(personToEdit, editedPerson);
return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson));
}
@@ -96,10 +97,11 @@ 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());
- Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags());
+ Role updatedRole = editPersonDescriptor.getRole().orElse(personToEdit.getRole());
+ Description updatedDescription =
+ editPersonDescriptor.getDescription().orElse(personToEdit.getDescription());
- return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags);
+ return new Person(updatedName, updatedPhone, updatedEmail, updatedRole, updatedDescription);
}
@Override
@@ -128,8 +130,8 @@ public static class EditPersonDescriptor {
private Name name;
private Phone phone;
private Email email;
- private Address address;
- private Set tags;
+ private Role role;
+ private Description description;
public EditPersonDescriptor() {}
@@ -141,15 +143,15 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) {
setName(toCopy.name);
setPhone(toCopy.phone);
setEmail(toCopy.email);
- setAddress(toCopy.address);
- setTags(toCopy.tags);
+ setRole(toCopy.role);
+ setDescription(toCopy.description);
}
/**
* 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, role, description);
}
public void setName(Name name) {
@@ -176,31 +178,23 @@ public Optional getEmail() {
return Optional.ofNullable(email);
}
- public void setAddress(Address address) {
- this.address = address;
+ public void setRole(Role role) {
+ this.role = role;
}
- public Optional getAddress() {
- return Optional.ofNullable(address);
+ public Optional getRole() {
+ return Optional.ofNullable(role);
}
- /**
- * Sets {@code tags} to this object's {@code tags}.
- * A defensive copy of {@code tags} is used internally.
- */
- public void setTags(Set tags) {
- this.tags = (tags != null) ? new HashSet<>(tags) : null;
+ public void setDescription(Description description) {
+ this.description = description;
}
- /**
- * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException}
- * if modification is attempted.
- * Returns {@code Optional#empty()} if {@code tags} is null.
- */
- public Optional> getTags() {
- return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty();
+ public Optional getDescription() {
+ return Optional.ofNullable(description);
}
+
@Override
public boolean equals(Object other) {
// short circuit if same object
@@ -219,8 +213,8 @@ public boolean equals(Object other) {
return getName().equals(e.getName())
&& getPhone().equals(e.getPhone())
&& getEmail().equals(e.getEmail())
- && getAddress().equals(e.getAddress())
- && getTags().equals(e.getTags());
+ && getRole().equals(e.getRole())
+ && getDescription().equals(e.getDescription());
}
}
}
diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/person/FindCommand.java
similarity index 54%
rename from src/main/java/seedu/address/logic/commands/FindCommand.java
rename to src/main/java/seedu/address/logic/commands/person/FindCommand.java
index d6b19b0a0de..582da90f994 100644
--- a/src/main/java/seedu/address/logic/commands/FindCommand.java
+++ b/src/main/java/seedu/address/logic/commands/person/FindCommand.java
@@ -1,10 +1,11 @@
-package seedu.address.logic.commands;
+package seedu.address.logic.commands.person;
import static java.util.Objects.requireNonNull;
import seedu.address.commons.core.Messages;
-import seedu.address.model.Model;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.model.person.ModelPerson;
+import seedu.address.model.person.person.NameDescriptionContainsKeywordsPredicate;
/**
* Finds and lists all persons in address book whose name contains any of the argument keywords.
@@ -14,23 +15,24 @@ 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 contacts whose names and/or "
+ + "descriptions contain any of "
+ "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n"
+ "Parameters: KEYWORD [MORE_KEYWORDS]...\n"
- + "Example: " + COMMAND_WORD + " alice bob charlie";
+ + "Example: " + COMMAND_WORD + " alice bob user";
- private final NameContainsKeywordsPredicate predicate;
+ private final NameDescriptionContainsKeywordsPredicate predicate;
- public FindCommand(NameContainsKeywordsPredicate predicate) {
+ public FindCommand(NameDescriptionContainsKeywordsPredicate predicate) {
this.predicate = predicate;
}
@Override
- public CommandResult execute(Model model) {
- requireNonNull(model);
- model.updateFilteredPersonList(predicate);
+ public CommandResult execute(ModelPerson modelPerson) {
+ requireNonNull(modelPerson);
+ modelPerson.updateFilteredPersonList(predicate);
return new CommandResult(
- String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size()));
+ String.format(Messages.MESSAGE_CONTACTS_LISTED_OVERVIEW, modelPerson.getFilteredPersonList().size()));
}
@Override
diff --git a/src/main/java/seedu/address/logic/commands/person/ListCommand.java b/src/main/java/seedu/address/logic/commands/person/ListCommand.java
new file mode 100644
index 00000000000..b28587a146b
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/person/ListCommand.java
@@ -0,0 +1,31 @@
+package seedu.address.logic.commands.person;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.model.person.ModelPerson.PREDICATE_SHOW_ALL_PERSONS;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.person.ModelPerson;
+
+/**
+ * Lists all persons in the address book to the user.
+ */
+public class ListCommand extends Command {
+
+ public static final String COMMAND_WORD = "list";
+
+ public static final String MESSAGE_SUCCESS = "Listed all contact(s)!";
+
+ @Override
+ public CommandResult execute(ModelPerson modelPerson) throws CommandException {
+ requireNonNull(modelPerson);
+
+ if (modelPerson.getInternalPersonList().size() == 0) {
+ throw new CommandException(String.format(Messages.MESSAGE_INVALID_CONTACT_LIST_EMPTY, COMMAND_WORD));
+ }
+
+ modelPerson.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ return new CommandResult(MESSAGE_SUCCESS);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/person/ViewCommand.java b/src/main/java/seedu/address/logic/commands/person/ViewCommand.java
new file mode 100644
index 00000000000..5fa3bc7411d
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/person/ViewCommand.java
@@ -0,0 +1,50 @@
+package seedu.address.logic.commands.person;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.person.ModelPerson;
+import seedu.address.model.person.person.Person;
+
+/**
+ * View a contact by displaying its details in the side panel
+ */
+public class ViewCommand extends Command {
+ public static final String COMMAND_WORD = "view";
+ public static final String MESSAGE_VIEW_PERSON_SUCCESS = "Displayed contact: %1$s";
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Displays the details of the contact identified by the index number used in the displayed contact list.\n"
+ + "Parameters: INDEX (must be a positive integer)\n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ private final Index targetIndex;
+
+ /**
+ * Construct command given index of contact to view.
+ * @param targetIndex specified index of contact to view.
+ */
+ public ViewCommand(Index targetIndex) {
+ requireNonNull(targetIndex);
+ this.targetIndex = targetIndex;
+ }
+
+
+ @Override
+ public CommandResult execute(ModelPerson modelPerson) throws CommandException {
+ requireNonNull(modelPerson);
+ List lastShownList = modelPerson.getFilteredPersonList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_CONTACT_DISPLAYED_INDEX);
+ }
+
+ Person personToView = lastShownList.get(targetIndex.getZeroBased());
+ modelPerson.setPersonInView(personToView);
+ return new CommandResult(String.format(MESSAGE_VIEW_PERSON_SUCCESS, personToView));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java
deleted file mode 100644
index 3b8bfa035e8..00000000000
--- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java
+++ /dev/null
@@ -1,60 +0,0 @@
-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.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-
-import java.util.Set;
-import java.util.stream.Stream;
-
-import seedu.address.logic.commands.AddCommand;
-import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
-
-/**
- * Parses input arguments and creates a new AddCommand object
- */
-public class AddCommandParser 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 AddCommand parse(String args) throws ParseException {
- ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
-
- if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL)
- || !argMultimap.getPreamble().isEmpty()) {
- throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
- }
-
- Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get());
- Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get());
- Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get());
- Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get());
- Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG));
-
- Person person = new Person(name, phone, email, address, tagList);
-
- 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/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
deleted file mode 100644
index 1e466792b46..00000000000
--- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package seedu.address.logic.parser;
-
-import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import seedu.address.logic.commands.AddCommand;
-import seedu.address.logic.commands.ClearCommand;
-import seedu.address.logic.commands.Command;
-import seedu.address.logic.commands.DeleteCommand;
-import seedu.address.logic.commands.EditCommand;
-import seedu.address.logic.commands.ExitCommand;
-import seedu.address.logic.commands.FindCommand;
-import seedu.address.logic.commands.HelpCommand;
-import seedu.address.logic.commands.ListCommand;
-import seedu.address.logic.parser.exceptions.ParseException;
-
-/**
- * Parses user input.
- */
-public class AddressBookParser {
-
- /**
- * Used for initial separation of command word and args.
- */
- private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)");
-
- /**
- * Parses user input into command for execution.
- *
- * @param userInput full user input string
- * @return the command based on the user input
- * @throws ParseException if the user input does not conform the expected format
- */
- public Command parseCommand(String userInput) throws ParseException {
- final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim());
- if (!matcher.matches()) {
- throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE));
- }
-
- final String commandWord = matcher.group("commandWord");
- final String arguments = matcher.group("arguments");
- switch (commandWord) {
-
- case AddCommand.COMMAND_WORD:
- return new AddCommandParser().parse(arguments);
-
- case EditCommand.COMMAND_WORD:
- return new EditCommandParser().parse(arguments);
-
- case DeleteCommand.COMMAND_WORD:
- return new DeleteCommandParser().parse(arguments);
-
- case ClearCommand.COMMAND_WORD:
- return new ClearCommand();
-
- case FindCommand.COMMAND_WORD:
- return new FindCommandParser().parse(arguments);
-
- case ListCommand.COMMAND_WORD:
- return new ListCommand();
-
- case ExitCommand.COMMAND_WORD:
- return new ExitCommand();
-
- case HelpCommand.COMMAND_WORD:
- return new HelpCommand();
-
- default:
- throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
- }
- }
-
-}
diff --git a/src/main/java/seedu/address/logic/parser/DateParser.java b/src/main/java/seedu/address/logic/parser/DateParser.java
new file mode 100644
index 00000000000..51f90d068d6
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/DateParser.java
@@ -0,0 +1,30 @@
+package seedu.address.logic.parser;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Represents a Parser that is able to parse Date.
+ */
+public class DateParser {
+ public static final String DATE_INVALID_FORMAT = "Date format should be either MM/DD/YYYY or MM/DD/YYYY HH:mm."
+ + "Note: Single digit (except for year) can start with leading zero.";
+
+ /**
+ * Parses the given {@code String} to Date
+ * @throws ParseException if the user input does not conform the expected date format
+ */
+ public static Date parseDate(String strDate) throws ParseException {
+
+ String formatWithMin = "y-M-d HH:mm";
+ String formatWithoutMin = "y-M-d";
+
+ try {
+ return new SimpleDateFormat(strDate.length() > 11 ? formatWithMin : formatWithoutMin).parse(strDate);
+ } catch (java.text.ParseException e) {
+ throw new ParseException(DATE_INVALID_FORMAT);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/seedu/address/logic/parser/Parser.java
index d6551ad8e3f..71adcea6dd5 100644
--- a/src/main/java/seedu/address/logic/parser/Parser.java
+++ b/src/main/java/seedu/address/logic/parser/Parser.java
@@ -1,12 +1,11 @@
package seedu.address.logic.parser;
-import seedu.address.logic.commands.Command;
import seedu.address.logic.parser.exceptions.ParseException;
/**
* Represents a Parser that is able to parse user input into a {@code Command} of type {@code T}.
*/
-public interface Parser {
+public interface Parser {
/**
* Parses {@code userInput} into a command and returns it.
diff --git a/src/main/java/seedu/address/logic/parser/TokenizedUserInput.java b/src/main/java/seedu/address/logic/parser/TokenizedUserInput.java
new file mode 100644
index 00000000000..07be7eb450b
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/TokenizedUserInput.java
@@ -0,0 +1,43 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import seedu.address.logic.commands.general.HelpCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+public class TokenizedUserInput {
+ /**
+ * Used for initial separation of command word and args.
+ */
+ private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)");
+
+ private final String commandWord;
+ private final String arguments;
+
+ TokenizedUserInput(String commandWord, String arguments) {
+ this.commandWord = commandWord;
+ this.arguments = arguments;
+ }
+
+ public static TokenizedUserInput getCommandWordArgumentsFromUserInput(String userInput) throws ParseException {
+ final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim());
+ if (!matcher.matches()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE));
+ }
+
+ final String commandWord = matcher.group("commandWord");
+ final String arguments = matcher.group("arguments");
+ return new TokenizedUserInput(commandWord, arguments);
+ }
+
+ public String getCommandWord() {
+ return commandWord;
+ }
+
+ public String getArguments() {
+ return arguments;
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/deliverable/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/deliverable/AddCommandParser.java
new file mode 100644
index 00000000000..ee079909069
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/deliverable/AddCommandParser.java
@@ -0,0 +1,62 @@
+package seedu.address.logic.parser.deliverable;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.deliverable.CliSyntax.PREFIX_CONTACTS;
+import static seedu.address.logic.parser.deliverable.CliSyntax.PREFIX_DEADLINE;
+import static seedu.address.logic.parser.deliverable.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.deliverable.CliSyntax.PREFIX_MILESTONE;
+import static seedu.address.logic.parser.deliverable.CliSyntax.PREFIX_TITLE;
+
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.deliverable.AddCommand;
+import seedu.address.logic.parser.ArgumentMultimap;
+import seedu.address.logic.parser.ArgumentTokenizer;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.Prefix;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.logic.parser.util.ParserCommon;
+import seedu.address.model.deliverable.deliverable.Deadline;
+import seedu.address.model.deliverable.deliverable.Deliverable;
+import seedu.address.model.deliverable.deliverable.Milestone;
+import seedu.address.model.util.Contacts;
+import seedu.address.model.util.Description;
+import seedu.address.model.util.Title;
+
+/**
+ * Parses input arguments and creates a new AddCommand object for deliverable
+ */
+public class AddCommandParser 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 AddCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_TITLE, PREFIX_MILESTONE,
+ PREFIX_DESCRIPTION, PREFIX_DEADLINE, PREFIX_CONTACTS);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_TITLE, PREFIX_MILESTONE,
+ PREFIX_DEADLINE) || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
+ }
+
+ Title title = ParserUtil.parseTitle(argMultimap.getValue(PREFIX_TITLE).get());
+ Milestone milestone = ParserUtil.parseMilestone(argMultimap.getValue(PREFIX_MILESTONE).get());
+ Description description = ParserCommon.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION));
+ Deadline deadline = ParserUtil.parseDeadline(argMultimap.getValue(PREFIX_DEADLINE).get());
+ Contacts contacts = ParserCommon.parseContacts(argMultimap.getValue(PREFIX_CONTACTS));
+
+ Deliverable deliverable = new Deliverable(title, milestone, description, deadline, contacts);
+ return new AddCommand(deliverable);
+ }
+
+ /**
+ * 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/deliverable/CliSyntax.java b/src/main/java/seedu/address/logic/parser/deliverable/CliSyntax.java
new file mode 100644
index 00000000000..bc9cd66da5f
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/deliverable/CliSyntax.java
@@ -0,0 +1,17 @@
+package seedu.address.logic.parser.deliverable;
+
+import seedu.address.logic.parser.Prefix;
+
+/**
+ * Contains Command Line Interface (CLI) syntax definitions common to deliverable commands
+ */
+public class CliSyntax {
+
+ /* Prefix definitions */
+ public static final Prefix PREFIX_TITLE = new Prefix("t/");
+ public static final Prefix PREFIX_MILESTONE = new Prefix("m/");
+ public static final Prefix PREFIX_DESCRIPTION = new Prefix("d/");
+ public static final Prefix PREFIX_DEADLINE = new Prefix("by/");
+ public static final Prefix PREFIX_CONTACTS = new Prefix("c/");
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/deliverable/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/deliverable/DeleteCommandParser.java
new file mode 100644
index 00000000000..c8de1e8cf98
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/deliverable/DeleteCommandParser.java
@@ -0,0 +1,30 @@
+package seedu.address.logic.parser.deliverable;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.deliverable.DeleteCommand;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new DeleteCommand object
+ */
+public class DeleteCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the DeleteCommand
+ * and returns a DeleteCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public DeleteCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new DeleteCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe);
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/deliverable/DeliverableBookParser.java b/src/main/java/seedu/address/logic/parser/deliverable/DeliverableBookParser.java
new file mode 100644
index 00000000000..b7692c15283
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/deliverable/DeliverableBookParser.java
@@ -0,0 +1,59 @@
+package seedu.address.logic.parser.deliverable;
+
+import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND;
+
+import seedu.address.logic.commands.deliverable.AddCommand;
+import seedu.address.logic.commands.deliverable.ClearCommand;
+import seedu.address.logic.commands.deliverable.Command;
+import seedu.address.logic.commands.deliverable.DeleteCommand;
+import seedu.address.logic.commands.deliverable.EditCommand;
+import seedu.address.logic.commands.deliverable.ListCommand;
+import seedu.address.logic.commands.deliverable.MarkDoneCommand;
+import seedu.address.logic.commands.deliverable.MarkUndoneCommand;
+import seedu.address.logic.commands.deliverable.ViewCommand;
+import seedu.address.logic.commands.person.FindCommand;
+import seedu.address.logic.parser.TokenizedUserInput;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses user input for deliverable.
+ */
+public class DeliverableBookParser {
+
+ /**
+ * Parses user input into command for execution.
+ *
+ * @param userInput full user input string
+ * @return the command based on the user input
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public Command parseCommand(String userInput) throws ParseException {
+ TokenizedUserInput tokenizedUserInput = TokenizedUserInput.getCommandWordArgumentsFromUserInput(userInput);
+ String commandWord = tokenizedUserInput.getCommandWord();
+ String arguments = tokenizedUserInput.getArguments();
+
+ switch (commandWord) {
+
+ case AddCommand.COMMAND_WORD:
+ return new AddCommandParser().parse(arguments);
+ case DeleteCommand.COMMAND_WORD:
+ return new DeleteCommandParser().parse(arguments);
+ case MarkDoneCommand.COMMAND_WORD:
+ return new MarkDoneCommandParser().parse(arguments);
+ case MarkUndoneCommand.COMMAND_WORD:
+ return new MarkUndoneCommandParser().parse(arguments);
+ case ViewCommand.COMMAND_WORD:
+ return new ViewCommandParser().parse(arguments);
+ case EditCommand.COMMAND_WORD:
+ return new EditCommandParser().parse(arguments);
+ case FindCommand.COMMAND_WORD:
+ return new FindCommandParser().parse(arguments);
+ case ListCommand.COMMAND_WORD:
+ return new ListCommand();
+ case ClearCommand.COMMAND_WORD:
+ return new ClearCommand();
+ default:
+ throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/deliverable/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/deliverable/EditCommandParser.java
new file mode 100644
index 00000000000..26d52093807
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/deliverable/EditCommandParser.java
@@ -0,0 +1,77 @@
+package seedu.address.logic.parser.deliverable;
+
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.deliverable.CliSyntax.PREFIX_CONTACTS;
+import static seedu.address.logic.parser.deliverable.CliSyntax.PREFIX_DEADLINE;
+import static seedu.address.logic.parser.deliverable.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.deliverable.CliSyntax.PREFIX_MILESTONE;
+import static seedu.address.logic.parser.deliverable.CliSyntax.PREFIX_TITLE;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.deliverable.EditCommand;
+import seedu.address.logic.commands.deliverable.EditCommand.EditDeliverableDescriptor;
+import seedu.address.logic.parser.ArgumentMultimap;
+import seedu.address.logic.parser.ArgumentTokenizer;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.logic.parser.util.ParserCommon;
+
+/**
+ * Parses input arguments and creates a new EditCommand object
+ */
+public class EditCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the EditCommand
+ * and returns an EditCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public EditCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_TITLE, PREFIX_MILESTONE, PREFIX_DESCRIPTION, PREFIX_DEADLINE,
+ PREFIX_CONTACTS);
+ Index index;
+
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe);
+ }
+
+ EditDeliverableDescriptor editDeliverableDescriptor = new EditDeliverableDescriptor();
+ if (argMultimap.getValue(PREFIX_TITLE).isPresent()) {
+ editDeliverableDescriptor.setTitle(ParserUtil.parseTitle(argMultimap.getValue(PREFIX_TITLE).get()));
+ }
+
+ if (argMultimap.getValue(PREFIX_MILESTONE).isPresent()) {
+ editDeliverableDescriptor.setMilestone(ParserUtil.parseMilestone(
+ argMultimap.getValue(PREFIX_MILESTONE).get()));
+ }
+
+ // Description takes optional String
+ if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) {
+ editDeliverableDescriptor.setDescription(ParserCommon.parseDescription(
+ argMultimap.getValue(PREFIX_DESCRIPTION)));
+ }
+
+ if (argMultimap.getValue(PREFIX_DEADLINE).isPresent()) {
+ editDeliverableDescriptor.setDeadline(ParserUtil.parseDeadline(
+ argMultimap.getValue(PREFIX_DEADLINE).get()));
+ }
+
+ // Contacts takes optional String
+ if (argMultimap.getValue(PREFIX_CONTACTS).isPresent()) {
+ editDeliverableDescriptor.setContacts(ParserCommon.parseContacts(argMultimap.getValue(PREFIX_CONTACTS)));
+ }
+
+ if (!editDeliverableDescriptor.isAnyFieldEdited()) {
+ throw new ParseException(EditCommand.MESSAGE_NOT_EDITED);
+ }
+
+ return new EditCommand(index, editDeliverableDescriptor);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/deliverable/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/deliverable/FindCommandParser.java
new file mode 100644
index 00000000000..47b9ccb34aa
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/deliverable/FindCommandParser.java
@@ -0,0 +1,32 @@
+package seedu.address.logic.parser.deliverable;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.util.Arrays;
+
+import seedu.address.logic.commands.deliverable.FindCommand;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.deliverable.deliverable.TitleDescriptionContainsKeywordsPredicate;
+
+/**
+ * Parses input arguments and creates a new FindCommand object
+ */
+public class FindCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the FindCommand
+ * and returns a FindCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public FindCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
+ }
+
+ String[] nameKeywords = trimmedArgs.replaceAll("\\W", " ").split("\\s+");
+
+ return new FindCommand(new TitleDescriptionContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/deliverable/MarkDoneCommandParser.java b/src/main/java/seedu/address/logic/parser/deliverable/MarkDoneCommandParser.java
new file mode 100644
index 00000000000..a4898f3358b
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/deliverable/MarkDoneCommandParser.java
@@ -0,0 +1,30 @@
+package seedu.address.logic.parser.deliverable;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.deliverable.MarkDoneCommand;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new MarkDoneCommand object
+ */
+public class MarkDoneCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the MarkDoneCommand
+ * and returns a MarkDoneCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ @Override
+ public MarkDoneCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new MarkDoneCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, MarkDoneCommand.MESSAGE_USAGE), pe);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/deliverable/MarkUndoneCommandParser.java b/src/main/java/seedu/address/logic/parser/deliverable/MarkUndoneCommandParser.java
new file mode 100644
index 00000000000..239b12ab8c8
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/deliverable/MarkUndoneCommandParser.java
@@ -0,0 +1,22 @@
+package seedu.address.logic.parser.deliverable;
+
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.deliverable.MarkUndoneCommand;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+public class MarkUndoneCommandParser implements Parser {
+ @Override
+ public MarkUndoneCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new MarkUndoneCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, MarkUndoneCommand.MESSAGE_USAGE), pe);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/deliverable/ParserUtil.java b/src/main/java/seedu/address/logic/parser/deliverable/ParserUtil.java
new file mode 100644
index 00000000000..7c5d9967217
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/deliverable/ParserUtil.java
@@ -0,0 +1,76 @@
+package seedu.address.logic.parser.deliverable;
+
+import static java.util.Objects.requireNonNull;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.util.StringUtil;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.deliverable.deliverable.Deadline;
+import seedu.address.model.deliverable.deliverable.Milestone;
+import seedu.address.model.util.Title;
+
+/**
+ * Contains utility methods used for parsing strings in the various *Parser classes.
+ */
+public class ParserUtil {
+
+ public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer.";
+
+ /**
+ * 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 {
+ String trimmedIndex = oneBasedIndex.trim();
+ if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) {
+ throw new ParseException(MESSAGE_INVALID_INDEX);
+ }
+ return Index.fromOneBased(Integer.parseInt(trimmedIndex));
+ }
+
+ /**
+ * Parses a {@code String title} into a {@code Title}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code title} is invalid.
+ */
+ public static Title parseTitle(String title) throws ParseException {
+ requireNonNull(title);
+ String trimmedTitle = title.trim();
+ if (!Title.isValidTitle(trimmedTitle)) {
+ throw new ParseException(Title.MESSAGE_CONSTRAINTS);
+ }
+ return new Title(trimmedTitle);
+ }
+
+ /**
+ * Parses a {@code String milestone} into a {@code Milestone}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException
+ */
+ public static Milestone parseMilestone(String milestone) throws ParseException {
+ requireNonNull(milestone);
+ String trimmedMilestone = milestone.trim();
+ if (!Milestone.isValidMilestone(trimmedMilestone)) {
+ throw new ParseException(Milestone.MESSAGE_CONSTRAINTS);
+ }
+ return new Milestone(trimmedMilestone);
+ }
+
+ /**
+ * Parses a {@code String deadline} into a {@code Deadline}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code deadline} is invalid.
+ */
+ public static Deadline parseDeadline(String deadline) throws ParseException {
+ requireNonNull(deadline);
+ String trimmedDeadline = deadline.trim();
+ if (!Deadline.isValidDeadline(trimmedDeadline)) {
+ throw new ParseException(Deadline.MESSAGE_CONSTRAINTS);
+ }
+ return new Deadline(trimmedDeadline);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/deliverable/ViewCommandParser.java b/src/main/java/seedu/address/logic/parser/deliverable/ViewCommandParser.java
new file mode 100644
index 00000000000..cfe19be8f8b
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/deliverable/ViewCommandParser.java
@@ -0,0 +1,30 @@
+package seedu.address.logic.parser.deliverable;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.deliverable.ViewCommand;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new ViewCommand object
+ */
+public class ViewCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the ViewCommand
+ * and returns a ViewCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ @Override
+ public ViewCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new ViewCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE), pe);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/general/GeneralParser.java b/src/main/java/seedu/address/logic/parser/general/GeneralParser.java
new file mode 100644
index 00000000000..1a6517e3c32
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/general/GeneralParser.java
@@ -0,0 +1,58 @@
+package seedu.address.logic.parser.general;
+
+import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND;
+
+import seedu.address.logic.commands.general.Command;
+import seedu.address.logic.commands.general.ExitCommand;
+import seedu.address.logic.commands.general.HelpCommand;
+import seedu.address.logic.commands.general.SwitchCommand;
+import seedu.address.logic.parser.TokenizedUserInput;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses user input for General Commands.
+ */
+public class GeneralParser {
+ /**
+ * Parses user input into command for execution.
+ *
+ * @param userInput full user input string
+ * @return the command based on the user input
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public Command parseCommand(String userInput) throws ParseException {
+ TokenizedUserInput tokenizedUserInput = TokenizedUserInput.getCommandWordArgumentsFromUserInput(userInput);
+ String commandWord = tokenizedUserInput.getCommandWord();
+ String arguments = tokenizedUserInput.getArguments();
+
+ switch (commandWord) {
+
+ case ExitCommand.COMMAND_WORD:
+ return new ExitCommand();
+
+ case HelpCommand.COMMAND_WORD:
+ return new HelpCommand();
+
+ case SwitchCommand.COMMAND_WORD:
+ return new SwitchCommandParser().parse(arguments);
+
+ default:
+ throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
+ }
+
+ }
+
+ /**
+ * Checks if user input is a general command.
+ *
+ * @param userInput full user input string
+ * @return whether the user input is a general command
+ */
+ public boolean isGeneralCommand(String userInput) throws ParseException {
+ TokenizedUserInput tokenizedUserInput = TokenizedUserInput.getCommandWordArgumentsFromUserInput(userInput);
+ String commandWord = tokenizedUserInput.getCommandWord();
+ return commandWord.equals(SwitchCommand.COMMAND_WORD) || commandWord.equals(ExitCommand.COMMAND_WORD)
+ || commandWord.equals(HelpCommand.COMMAND_WORD);
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/general/SwitchCommandParser.java b/src/main/java/seedu/address/logic/parser/general/SwitchCommandParser.java
new file mode 100644
index 00000000000..4693afcca41
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/general/SwitchCommandParser.java
@@ -0,0 +1,37 @@
+package seedu.address.logic.parser.general;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.commons.ModeEnum;
+import seedu.address.logic.commands.general.SwitchCommand;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new SwitchCommand object
+ */
+public class SwitchCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the SwitchCommand
+ * and returns a SwitchCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public SwitchCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, SwitchCommand.MESSAGE_USAGE));
+ }
+
+ ModeEnum newMode = ModeEnum.getEnumByArgument(trimmedArgs);
+
+ if (newMode == null) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, SwitchCommand.MESSAGE_USAGE));
+ }
+
+ return new SwitchCommand(newMode);
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/meeting/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/meeting/AddCommandParser.java
new file mode 100644
index 00000000000..2436124eafd
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/meeting/AddCommandParser.java
@@ -0,0 +1,76 @@
+package seedu.address.logic.parser.meeting;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.meeting.CliSyntax.PREFIX_CONTACTS;
+import static seedu.address.logic.parser.meeting.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.meeting.CliSyntax.PREFIX_FROM;
+import static seedu.address.logic.parser.meeting.CliSyntax.PREFIX_LOCATION;
+import static seedu.address.logic.parser.meeting.CliSyntax.PREFIX_TITLE;
+import static seedu.address.logic.parser.meeting.CliSyntax.PREFIX_TO;
+
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.meeting.AddCommand;
+import seedu.address.logic.parser.ArgumentMultimap;
+import seedu.address.logic.parser.ArgumentTokenizer;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.Prefix;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.logic.parser.util.ParserCommon;
+import seedu.address.model.meeting.meeting.From;
+import seedu.address.model.meeting.meeting.Location;
+import seedu.address.model.meeting.meeting.Meeting;
+import seedu.address.model.meeting.meeting.To;
+import seedu.address.model.util.Contacts;
+import seedu.address.model.util.Description;
+import seedu.address.model.util.Title;
+
+/**
+ * Parses input arguments and creates a new AddCommand object
+ */
+public class AddCommandParser 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 AddCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_TITLE, PREFIX_DESCRIPTION, PREFIX_TO, PREFIX_FROM,
+ PREFIX_CONTACTS, PREFIX_LOCATION);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_TITLE, PREFIX_FROM, PREFIX_TO)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
+ }
+
+ Title title = ParserUtil.parseTitle(argMultimap.getValue(PREFIX_TITLE).get());
+ Description description = ParserCommon.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION));
+ From from = ParserUtil.parseFrom(argMultimap.getValue(PREFIX_FROM).get());
+ To to = ParserUtil.parseTo(argMultimap.getValue(PREFIX_TO).get());
+ Contacts contacts = ParserCommon.parseContacts(argMultimap.getValue(PREFIX_CONTACTS));
+ Location location = ParserUtil.parseLocation(argMultimap.getValue(PREFIX_LOCATION));
+
+ Meeting meeting;
+
+ try {
+ meeting = new Meeting(title, description, from, to, contacts, location);
+ } catch (IllegalArgumentException e) {
+ throw new ParseException(e.getMessage());
+ }
+
+ assert Meeting.isValidFromAndTo(meeting.getFrom(), meeting.getTo()) : "From should be earlier than To";
+
+ return new AddCommand(meeting);
+
+ }
+
+ /**
+ * 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/meeting/CliSyntax.java b/src/main/java/seedu/address/logic/parser/meeting/CliSyntax.java
new file mode 100644
index 00000000000..8f064bad85e
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/meeting/CliSyntax.java
@@ -0,0 +1,17 @@
+package seedu.address.logic.parser.meeting;
+
+import seedu.address.logic.parser.Prefix;
+
+/**
+ * Contains Command Line Interface (CLI) syntax definitions common to multiple commands
+ */
+public class CliSyntax {
+ /* Prefix definitions */
+ public static final Prefix PREFIX_TITLE = new Prefix("t/");
+ public static final Prefix PREFIX_DESCRIPTION = new Prefix("d/");
+ public static final Prefix PREFIX_FROM = new Prefix("from/");
+ public static final Prefix PREFIX_TO = new Prefix("to/");
+ public static final Prefix PREFIX_CONTACTS = new Prefix("c/");
+ public static final Prefix PREFIX_LOCATION = new Prefix("l/");
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/meeting/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/meeting/DeleteCommandParser.java
new file mode 100644
index 00000000000..311dfb11b63
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/meeting/DeleteCommandParser.java
@@ -0,0 +1,29 @@
+package seedu.address.logic.parser.meeting;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.meeting.DeleteCommand;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new DeleteCommand object
+ */
+
+public class DeleteCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the DeleteCommand
+ * and returns a DeleteCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public DeleteCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new DeleteCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/meeting/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/meeting/EditCommandParser.java
new file mode 100644
index 00000000000..217aebb145e
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/meeting/EditCommandParser.java
@@ -0,0 +1,82 @@
+package seedu.address.logic.parser.meeting;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.meeting.CliSyntax.PREFIX_CONTACTS;
+import static seedu.address.logic.parser.meeting.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.meeting.CliSyntax.PREFIX_FROM;
+import static seedu.address.logic.parser.meeting.CliSyntax.PREFIX_LOCATION;
+import static seedu.address.logic.parser.meeting.CliSyntax.PREFIX_TITLE;
+import static seedu.address.logic.parser.meeting.CliSyntax.PREFIX_TO;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.meeting.EditCommand;
+import seedu.address.logic.commands.meeting.EditCommand.EditMeetingDescriptor;
+import seedu.address.logic.parser.ArgumentMultimap;
+import seedu.address.logic.parser.ArgumentTokenizer;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.logic.parser.util.ParserCommon;
+
+/**
+ * Parses input arguments and creates a new EditCommand object
+ */
+public class EditCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the EditCommand
+ * and returns an EditCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public EditCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_TITLE, PREFIX_DESCRIPTION, PREFIX_TO, PREFIX_FROM,
+ PREFIX_CONTACTS, PREFIX_LOCATION);
+ Index index;
+
+ try {
+ index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe);
+ }
+
+ EditMeetingDescriptor editMeetingDescriptor = new EditMeetingDescriptor();
+ if (argMultimap.getValue(PREFIX_TITLE).isPresent()) {
+ editMeetingDescriptor.setTitle(ParserUtil.parseTitle(argMultimap.getValue(PREFIX_TITLE).get()));
+ }
+
+ // Description takes optional String
+ if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) {
+ editMeetingDescriptor.setDescription(ParserCommon.parseDescription(
+ argMultimap.getValue(PREFIX_DESCRIPTION)));
+ }
+
+ if (argMultimap.getValue(PREFIX_FROM).isPresent()) {
+ editMeetingDescriptor.setFrom(ParserUtil.parseFrom(argMultimap.getValue(PREFIX_FROM).get()));
+ }
+
+ if (argMultimap.getValue(PREFIX_TO).isPresent()) {
+ editMeetingDescriptor.setTo(ParserUtil.parseTo(argMultimap.getValue(PREFIX_TO).get()));
+ }
+
+ // Contacts takes optional String
+ if (argMultimap.getValue(PREFIX_CONTACTS).isPresent()) {
+ editMeetingDescriptor.setContacts(ParserCommon.parseContacts(argMultimap
+ .getValue(PREFIX_CONTACTS)));
+ }
+
+ // Location takes optional String
+ if (argMultimap.getValue(PREFIX_LOCATION).isPresent()) {
+ editMeetingDescriptor.setLocation(ParserUtil.parseLocation(argMultimap
+ .getValue(PREFIX_LOCATION)));
+ }
+
+ if (!editMeetingDescriptor.isAnyFieldEdited()) {
+ throw new ParseException(EditCommand.MESSAGE_NOT_EDITED);
+ }
+
+ return new EditCommand(index, editMeetingDescriptor);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/meeting/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/meeting/FindCommandParser.java
new file mode 100644
index 00000000000..9448c75470e
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/meeting/FindCommandParser.java
@@ -0,0 +1,31 @@
+package seedu.address.logic.parser.meeting;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.util.Arrays;
+
+import seedu.address.logic.commands.meeting.FindCommand;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.meeting.meeting.TitleDescriptionContainsKeywordsPredicate;
+
+
+public class FindCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the FindCommand
+ * and returns a FindCommand object for execution.
+ *
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public FindCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+ if (trimmedArgs.isEmpty()) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
+ }
+
+ String[] nameKeywords = trimmedArgs.replaceAll("\\W", " ").split("\\s+");
+
+ return new FindCommand(new TitleDescriptionContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/meeting/MeetingBookParser.java b/src/main/java/seedu/address/logic/parser/meeting/MeetingBookParser.java
new file mode 100644
index 00000000000..06056c225b7
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/meeting/MeetingBookParser.java
@@ -0,0 +1,72 @@
+package seedu.address.logic.parser.meeting;
+
+import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND;
+
+import seedu.address.logic.commands.meeting.ClearCommand;
+import seedu.address.logic.commands.meeting.Command;
+import seedu.address.logic.commands.meeting.DeleteCommand;
+import seedu.address.logic.commands.meeting.EditCommand;
+import seedu.address.logic.commands.meeting.FindCommand;
+import seedu.address.logic.commands.meeting.ListCommand;
+import seedu.address.logic.commands.meeting.ViewCommand;
+import seedu.address.logic.commands.person.AddCommand;
+import seedu.address.logic.parser.TokenizedUserInput;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses user input for meeting.
+ */
+public class MeetingBookParser {
+
+ /**
+ * Parses user input into command for execution.
+ *
+ * @param userInput full user input string
+ * @return the command based on the user input
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public Command parseCommand(String userInput) throws ParseException {
+ TokenizedUserInput tokenizedUserInput = TokenizedUserInput.getCommandWordArgumentsFromUserInput(userInput);
+ String commandWord = tokenizedUserInput.getCommandWord();
+ String arguments = tokenizedUserInput.getArguments();
+
+ switch (commandWord) {
+
+ case AddCommand.COMMAND_WORD:
+ return new AddCommandParser().parse(arguments);
+
+ case DeleteCommand.COMMAND_WORD:
+ return new DeleteCommandParser().parse(arguments);
+
+ case EditCommand.COMMAND_WORD:
+ return new EditCommandParser().parse(arguments);
+
+ case ViewCommand.COMMAND_WORD:
+ return new ViewCommandParser().parse(arguments);
+
+ case ClearCommand.COMMAND_WORD:
+ return new ClearCommand();
+
+ case FindCommand.COMMAND_WORD:
+ return new FindCommandParser().parse(arguments);
+
+ case ListCommand.COMMAND_WORD:
+ return new ListCommand();
+
+ default:
+ throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
+ }
+
+ }
+
+ /**
+ * Checks if user input is a mode command.
+ *
+ * @param userInput full user input string
+ * @return whether the user input is a mode command
+ */
+ public boolean isMeetingCommand(String userInput) {
+ return userInput.startsWith(Command.COMMAND_WORD);
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/meeting/ParserUtil.java b/src/main/java/seedu/address/logic/parser/meeting/ParserUtil.java
new file mode 100644
index 00000000000..4186dcec58d
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/meeting/ParserUtil.java
@@ -0,0 +1,95 @@
+package seedu.address.logic.parser.meeting;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Optional;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.util.StringUtil;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.meeting.meeting.From;
+import seedu.address.model.meeting.meeting.Location;
+import seedu.address.model.meeting.meeting.To;
+import seedu.address.model.util.Title;
+
+/**
+ * Contains utility methods used for parsing strings in the various *Parser classes.
+ */
+public class ParserUtil {
+ public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer.";
+
+ /**
+ * 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 {
+ String trimmedIndex = oneBasedIndex.trim();
+ if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) {
+ throw new ParseException(MESSAGE_INVALID_INDEX);
+ }
+ return Index.fromOneBased(Integer.parseInt(trimmedIndex));
+ }
+
+ /**
+ * Parses a {@code String title} into a {@code Title}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code name} is invalid.
+ */
+ public static Title parseTitle(String title) throws ParseException {
+ requireNonNull(title);
+ String trimmedTitle = title.trim();
+ if (!Title.isValidTitle(trimmedTitle)) {
+ throw new ParseException(Title.MESSAGE_CONSTRAINTS);
+ }
+ return new Title(trimmedTitle);
+ }
+
+ /**
+ * Parses a {@code String address} into an {@code Address}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code address} is invalid.
+ */
+ public static To parseTo(String to) throws ParseException {
+ requireNonNull(to);
+ String trimmedTo = to.trim();
+ if (!To.isValidTo(trimmedTo)) {
+ throw new ParseException(To.MESSAGE_CONSTRAINTS);
+ }
+ return new To(trimmedTo);
+ }
+
+ /**
+ * Parses a {@code String address} into an {@code Address}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code address} is invalid.
+ */
+ public static From parseFrom(String from) throws ParseException {
+ requireNonNull(from);
+ String trimmedFrom = from.trim();
+ if (!From.isValidFrom(trimmedFrom)) {
+ throw new ParseException(From.MESSAGE_CONSTRAINTS);
+ }
+ return new From(trimmedFrom);
+ }
+
+ /**
+ * Parses a {@code String address} into an {@code Address}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code location} is invalid.
+ */
+ public static Location parseLocation(Optional location) throws ParseException {
+ if (location.isEmpty()) {
+ return new Location(location);
+ }
+ String trimmedLocation = location.get().trim();
+ if (!Location.isValidLocation(trimmedLocation)) {
+ throw new ParseException(Location.MESSAGE_CONSTRAINTS);
+ }
+ return new Location(trimmedLocation);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/meeting/ViewCommandParser.java b/src/main/java/seedu/address/logic/parser/meeting/ViewCommandParser.java
new file mode 100644
index 00000000000..a4eb71d0a0b
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/meeting/ViewCommandParser.java
@@ -0,0 +1,30 @@
+package seedu.address.logic.parser.meeting;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.meeting.ViewCommand;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.deliverable.ParserUtil;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new ViewCommand object
+ */
+public class ViewCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the ViewCommand
+ * and returns a ViewCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ @Override
+ public ViewCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new ViewCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE), pe);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/person/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/person/AddCommandParser.java
new file mode 100644
index 00000000000..8e469b2ef30
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/person/AddCommandParser.java
@@ -0,0 +1,65 @@
+package seedu.address.logic.parser.person;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.person.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.person.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.person.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.person.CliSyntax.PREFIX_PHONE;
+import static seedu.address.logic.parser.person.CliSyntax.PREFIX_ROLE;
+
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.person.AddCommand;
+import seedu.address.logic.parser.ArgumentMultimap;
+import seedu.address.logic.parser.ArgumentTokenizer;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.Prefix;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.logic.parser.util.ParserCommon;
+import seedu.address.model.person.person.Email;
+import seedu.address.model.person.person.Name;
+import seedu.address.model.person.person.Person;
+import seedu.address.model.person.person.Phone;
+import seedu.address.model.person.person.Role;
+import seedu.address.model.util.Description;
+
+/**
+ * Parses input arguments and creates a new AddCommand object for person
+ */
+public class AddCommandParser 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 AddCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_ROLE, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL,
+ PREFIX_DESCRIPTION);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_ROLE, PREFIX_NAME, PREFIX_EMAIL)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
+ }
+
+ Role role = ParserUtil.parseRole(argMultimap.getValue(PREFIX_ROLE).get());
+ Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get());
+ Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE));
+ Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get());
+ Description description = ParserCommon.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION));
+
+ Person person = new Person(name, phone, email, role, description);
+
+ 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/person/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/person/AddressBookParser.java
new file mode 100644
index 00000000000..eff5c19a392
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/person/AddressBookParser.java
@@ -0,0 +1,61 @@
+package seedu.address.logic.parser.person;
+
+import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND;
+
+import seedu.address.logic.commands.person.AddCommand;
+import seedu.address.logic.commands.person.ClearCommand;
+import seedu.address.logic.commands.person.Command;
+import seedu.address.logic.commands.person.DeleteCommand;
+import seedu.address.logic.commands.person.EditCommand;
+import seedu.address.logic.commands.person.FindCommand;
+import seedu.address.logic.commands.person.ListCommand;
+import seedu.address.logic.commands.person.ViewCommand;
+import seedu.address.logic.parser.TokenizedUserInput;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses user input.
+ */
+public class AddressBookParser {
+
+ /**
+ * Parses user input into command for execution.
+ *
+ * @param userInput full user input string
+ * @return the command based on the user input
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public Command parseCommand(String userInput) throws ParseException {
+ TokenizedUserInput tokenizedUserInput = TokenizedUserInput.getCommandWordArgumentsFromUserInput(userInput);
+ String commandWord = tokenizedUserInput.getCommandWord();
+ String arguments = tokenizedUserInput.getArguments();
+
+ switch (commandWord) {
+
+ case AddCommand.COMMAND_WORD:
+ return new AddCommandParser().parse(arguments);
+
+ case EditCommand.COMMAND_WORD:
+ return new EditCommandParser().parse(arguments);
+
+ case DeleteCommand.COMMAND_WORD:
+ return new DeleteCommandParser().parse(arguments);
+
+ case ViewCommand.COMMAND_WORD:
+ return new ViewCommandParser().parse(arguments);
+
+ case ClearCommand.COMMAND_WORD:
+ return new ClearCommand();
+
+ case FindCommand.COMMAND_WORD:
+ return new FindCommandParser().parse(arguments);
+
+ case ListCommand.COMMAND_WORD:
+ return new ListCommand();
+
+ default:
+ throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/person/CliSyntax.java
similarity index 61%
rename from src/main/java/seedu/address/logic/parser/CliSyntax.java
rename to src/main/java/seedu/address/logic/parser/person/CliSyntax.java
index 75b1a9bf119..ded78335680 100644
--- a/src/main/java/seedu/address/logic/parser/CliSyntax.java
+++ b/src/main/java/seedu/address/logic/parser/person/CliSyntax.java
@@ -1,4 +1,6 @@
-package seedu.address.logic.parser;
+package seedu.address.logic.parser.person;
+
+import seedu.address.logic.parser.Prefix;
/**
* Contains Command Line Interface (CLI) syntax definitions common to multiple commands
@@ -6,10 +8,10 @@
public class CliSyntax {
/* Prefix definitions */
+ public static final Prefix PREFIX_ROLE = new Prefix("r/");
public static final Prefix PREFIX_NAME = new Prefix("n/");
public static final Prefix PREFIX_PHONE = new Prefix("p/");
public static final Prefix PREFIX_EMAIL = new Prefix("e/");
- public static final Prefix PREFIX_ADDRESS = new Prefix("a/");
- public static final Prefix PREFIX_TAG = new Prefix("t/");
+ public static final Prefix PREFIX_DESCRIPTION = new Prefix("d/");
}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/person/DeleteCommandParser.java
similarity index 87%
rename from src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
rename to src/main/java/seedu/address/logic/parser/person/DeleteCommandParser.java
index 522b93081cc..c669b5f4353 100644
--- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/person/DeleteCommandParser.java
@@ -1,9 +1,10 @@
-package seedu.address.logic.parser;
+package seedu.address.logic.parser.person;
import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import seedu.address.commons.core.index.Index;
-import seedu.address.logic.commands.DeleteCommand;
+import seedu.address.logic.commands.person.DeleteCommand;
+import seedu.address.logic.parser.Parser;
import seedu.address.logic.parser.exceptions.ParseException;
/**
diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/person/EditCommandParser.java
similarity index 51%
rename from src/main/java/seedu/address/logic/parser/EditCommandParser.java
rename to src/main/java/seedu/address/logic/parser/person/EditCommandParser.java
index 845644b7dea..c28f887a124 100644
--- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/person/EditCommandParser.java
@@ -1,23 +1,21 @@
-package seedu.address.logic.parser;
+package seedu.address.logic.parser.person;
import static java.util.Objects.requireNonNull;
import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
-import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Optional;
-import java.util.Set;
+import static seedu.address.logic.parser.person.CliSyntax.PREFIX_DESCRIPTION;
+import static seedu.address.logic.parser.person.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.person.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.person.CliSyntax.PREFIX_PHONE;
+import static seedu.address.logic.parser.person.CliSyntax.PREFIX_ROLE;
import seedu.address.commons.core.index.Index;
-import seedu.address.logic.commands.EditCommand;
-import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
+import seedu.address.logic.commands.person.EditCommand;
+import seedu.address.logic.commands.person.EditCommand.EditPersonDescriptor;
+import seedu.address.logic.parser.ArgumentMultimap;
+import seedu.address.logic.parser.ArgumentTokenizer;
+import seedu.address.logic.parser.Parser;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.tag.Tag;
+import seedu.address.logic.parser.util.ParserCommon;
/**
* Parses input arguments and creates a new EditCommand object
@@ -32,7 +30,8 @@ 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_ROLE, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL,
+ PREFIX_DESCRIPTION);
Index index;
@@ -43,19 +42,22 @@ public EditCommand parse(String args) throws ParseException {
}
EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor();
+ if (argMultimap.getValue(PREFIX_ROLE).isPresent()) {
+ editPersonDescriptor.setRole(ParserUtil.parseRole(argMultimap.getValue(PREFIX_ROLE).get()));
+ }
if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()));
}
if (argMultimap.getValue(PREFIX_PHONE).isPresent()) {
- editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()));
+ editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE)));
}
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_DESCRIPTION).isPresent()) {
+ editPersonDescriptor.setDescription(ParserCommon.parseDescription(
+ argMultimap.getValue(PREFIX_DESCRIPTION)));
}
- parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags);
if (!editPersonDescriptor.isAnyFieldEdited()) {
throw new ParseException(EditCommand.MESSAGE_NOT_EDITED);
@@ -64,19 +66,4 @@ public EditCommand parse(String args) throws ParseException {
return new EditCommand(index, editPersonDescriptor);
}
- /**
- * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty.
- * If {@code tags} contain only one element which is an empty string, it will be parsed into a
- * {@code Set} containing zero tags.
- */
- private Optional> parseTagsForEdit(Collection tags) throws ParseException {
- assert tags != null;
-
- if (tags.isEmpty()) {
- return Optional.empty();
- }
- 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/person/FindCommandParser.java
similarity index 67%
rename from src/main/java/seedu/address/logic/parser/FindCommandParser.java
rename to src/main/java/seedu/address/logic/parser/person/FindCommandParser.java
index 4fb71f23103..b2ea7281ffa 100644
--- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/person/FindCommandParser.java
@@ -1,12 +1,13 @@
-package seedu.address.logic.parser;
+package seedu.address.logic.parser.person;
import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import java.util.Arrays;
-import seedu.address.logic.commands.FindCommand;
+import seedu.address.logic.commands.person.FindCommand;
+import seedu.address.logic.parser.Parser;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.person.person.NameDescriptionContainsKeywordsPredicate;
/**
* Parses input arguments and creates a new FindCommand object
@@ -25,9 +26,9 @@ public FindCommand parse(String args) throws ParseException {
String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
}
- String[] nameKeywords = trimmedArgs.split("\\s+");
+ String[] nameKeywords = trimmedArgs.replaceAll("\\W", " ").split("\\s+");
- return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
+ return new FindCommand(new NameDescriptionContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
}
}
diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/person/ParserUtil.java
similarity index 59%
rename from src/main/java/seedu/address/logic/parser/ParserUtil.java
rename to src/main/java/seedu/address/logic/parser/person/ParserUtil.java
index b117acb9c55..946b0989618 100644
--- a/src/main/java/seedu/address/logic/parser/ParserUtil.java
+++ b/src/main/java/seedu/address/logic/parser/person/ParserUtil.java
@@ -1,19 +1,16 @@
-package seedu.address.logic.parser;
+package seedu.address.logic.parser.person;
import static java.util.Objects.requireNonNull;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.Optional;
import seedu.address.commons.core.index.Index;
import seedu.address.commons.util.StringUtil;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
+import seedu.address.model.person.person.Email;
+import seedu.address.model.person.person.Name;
+import seedu.address.model.person.person.Phone;
+import seedu.address.model.person.person.Role;
/**
* Contains utility methods used for parsing strings in the various *Parser classes.
@@ -56,30 +53,18 @@ public static Name parseName(String name) throws ParseException {
*
* @throws ParseException if the given {@code phone} is invalid.
*/
- public static Phone parsePhone(String phone) throws ParseException {
+ public static Phone parsePhone(Optional phone) throws ParseException {
requireNonNull(phone);
- String trimmedPhone = phone.trim();
+ if (phone.isEmpty()) {
+ return new Phone(phone);
+ }
+ String trimmedPhone = phone.get().trim();
if (!Phone.isValidPhone(trimmedPhone)) {
throw new ParseException(Phone.MESSAGE_CONSTRAINTS);
}
return new Phone(trimmedPhone);
}
- /**
- * Parses a {@code String address} into an {@code Address}.
- * Leading and trailing whitespaces will be trimmed.
- *
- * @throws ParseException if the given {@code address} 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);
- }
- return new Address(trimmedAddress);
- }
-
/**
* Parses a {@code String email} into an {@code Email}.
* Leading and trailing whitespaces will be trimmed.
@@ -96,29 +81,17 @@ public static Email parseEmail(String email) throws ParseException {
}
/**
- * Parses a {@code String tag} into a {@code Tag}.
+ * Parses a {@code String role} into a {@code Role}.
* Leading and trailing whitespaces will be trimmed.
*
- * @throws ParseException if the given {@code tag} is invalid.
- */
- public static Tag parseTag(String tag) throws ParseException {
- requireNonNull(tag);
- String trimmedTag = tag.trim();
- if (!Tag.isValidTagName(trimmedTag)) {
- throw new ParseException(Tag.MESSAGE_CONSTRAINTS);
- }
- return new Tag(trimmedTag);
- }
-
- /**
- * Parses {@code Collection tags} into a {@code Set}.
+ * @throws ParseException if the given {@code role} is invalid.
*/
- public static Set parseTags(Collection tags) throws ParseException {
- requireNonNull(tags);
- final Set tagSet = new HashSet<>();
- for (String tagName : tags) {
- tagSet.add(parseTag(tagName));
+ public static Role parseRole(String role) throws ParseException {
+ requireNonNull(role);
+ String trimmedRole = role.trim();
+ if (!Role.isValidRole(trimmedRole)) {
+ throw new ParseException(Role.MESSAGE_CONSTRAINTS);
}
- return tagSet;
+ return Role.getRole(trimmedRole);
}
}
diff --git a/src/main/java/seedu/address/logic/parser/person/ViewCommandParser.java b/src/main/java/seedu/address/logic/parser/person/ViewCommandParser.java
new file mode 100644
index 00000000000..5a82afab5e0
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/person/ViewCommandParser.java
@@ -0,0 +1,30 @@
+package seedu.address.logic.parser.person;
+
+import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.person.ViewCommand;
+import seedu.address.logic.parser.Parser;
+import seedu.address.logic.parser.deliverable.ParserUtil;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new ViewCommand object
+ */
+public class ViewCommandParser implements Parser {
+ /**
+ * Parses the given {@code String} of arguments in the context of the ViewCommand
+ * and returns a ViewCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ @Override
+ public ViewCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseIndex(args);
+ return new ViewCommand(index);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE), pe);
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/util/ParserCommon.java b/src/main/java/seedu/address/logic/parser/util/ParserCommon.java
new file mode 100644
index 00000000000..f39a7ad88da
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/util/ParserCommon.java
@@ -0,0 +1,47 @@
+package seedu.address.logic.parser.util;
+
+import java.util.Optional;
+
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.util.Contacts;
+import seedu.address.model.util.Description;
+
+/**
+ * Contains utility methods used for parsing strings in the various *Parser classes.
+ */
+public class ParserCommon {
+
+ /**
+ * Parses a {@code Optional description} into a {@code Description}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code phone} is invalid.
+ */
+ public static Description parseDescription(Optional description) throws ParseException {
+ if (description.isEmpty()) {
+ return new Description(description);
+ }
+ String trimmedDescription = description.get().trim();
+ if (!Description.isValidDescription(trimmedDescription)) {
+ throw new ParseException(Description.MESSAGE_CONSTRAINTS);
+ }
+ return new Description(trimmedDescription);
+ }
+
+ /**
+ * Parses a {@code Optional contacts} into an {@code Contacts}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code contacts} is invalid.
+ */
+ public static Contacts parseContacts(Optional contacts) throws ParseException {
+ if (contacts.isEmpty()) {
+ return new Contacts(contacts);
+ }
+ String trimmedContacts = contacts.get().trim();
+ if (!Contacts.isValidContacts(trimmedContacts)) {
+ throw new ParseException(Contacts.MESSAGE_CONSTRAINTS);
+ }
+ return new Contacts(trimmedContacts);
+ }
+}
diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
index befd58a4c73..23c66ad4513 100644
--- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
+++ b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
@@ -13,4 +13,8 @@ public interface ReadOnlyUserPrefs {
Path getAddressBookFilePath();
+ Path getDeliverableBookFilePath();
+
+ Path getMeetingBookFilePath();
+
}
diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java
index 25a5fd6eab9..609a8e2895a 100644
--- a/src/main/java/seedu/address/model/UserPrefs.java
+++ b/src/main/java/seedu/address/model/UserPrefs.java
@@ -14,7 +14,9 @@
public class UserPrefs implements ReadOnlyUserPrefs {
private GuiSettings guiSettings = new GuiSettings();
- private Path addressBookFilePath = Paths.get("data" , "addressbook.json");
+ private Path addressBookFilePath = Paths.get("data" , "contactbook.json");
+ private Path deliverableBookFilePath = Paths.get("data", "deliverablebook.json");
+ private Path meetingBookFilePath = Paths.get("data", "meetingbook.json");
/**
* Creates a {@code UserPrefs} with default values.
@@ -36,6 +38,8 @@ public void resetData(ReadOnlyUserPrefs newUserPrefs) {
requireNonNull(newUserPrefs);
setGuiSettings(newUserPrefs.getGuiSettings());
setAddressBookFilePath(newUserPrefs.getAddressBookFilePath());
+ setDeliverableBookFilePath(newUserPrefs.getDeliverableBookFilePath());
+ setMeetingBookFilePath(newUserPrefs.getMeetingBookFilePath());
}
public GuiSettings getGuiSettings() {
@@ -51,11 +55,29 @@ public Path getAddressBookFilePath() {
return addressBookFilePath;
}
+ public Path getDeliverableBookFilePath() {
+ return deliverableBookFilePath;
+ }
+
+ public Path getMeetingBookFilePath() {
+ return meetingBookFilePath;
+ }
+
public void setAddressBookFilePath(Path addressBookFilePath) {
requireNonNull(addressBookFilePath);
this.addressBookFilePath = addressBookFilePath;
}
+ public void setDeliverableBookFilePath(Path deliverableBookFilePath) {
+ requireNonNull(deliverableBookFilePath);
+ this.deliverableBookFilePath = deliverableBookFilePath;
+ }
+
+ public void setMeetingBookFilePath(Path meetingBookFilePath) {
+ requireNonNull(meetingBookFilePath);
+ this.meetingBookFilePath = meetingBookFilePath;
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -68,19 +90,24 @@ public boolean equals(Object other) {
UserPrefs o = (UserPrefs) other;
return guiSettings.equals(o.guiSettings)
- && addressBookFilePath.equals(o.addressBookFilePath);
+ && addressBookFilePath.equals(o.addressBookFilePath)
+ && deliverableBookFilePath.equals(o.deliverableBookFilePath)
+ && meetingBookFilePath.equals(o.meetingBookFilePath);
}
@Override
public int hashCode() {
- return Objects.hash(guiSettings, addressBookFilePath);
+ return Objects.hash(guiSettings, addressBookFilePath,
+ deliverableBookFilePath, meetingBookFilePath);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Gui Settings : " + guiSettings);
- sb.append("\nLocal data file location : " + addressBookFilePath);
+ sb.append("\nLocal data address file location : " + addressBookFilePath);
+ sb.append("\nLocal data deliverable file location : " + addressBookFilePath);
+ sb.append("\nLocal data meeting file location : " + addressBookFilePath);
return sb.toString();
}
diff --git a/src/main/java/seedu/address/model/deliverable/DeliverableBook.java b/src/main/java/seedu/address/model/deliverable/DeliverableBook.java
new file mode 100644
index 00000000000..9b168f0575c
--- /dev/null
+++ b/src/main/java/seedu/address/model/deliverable/DeliverableBook.java
@@ -0,0 +1,129 @@
+package seedu.address.model.deliverable;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import javafx.collections.ObservableList;
+import seedu.address.model.deliverable.deliverable.Deliverable;
+import seedu.address.model.deliverable.deliverable.UniqueDeliverableList;
+
+/**
+ * Wraps all data at the deliverable-book level
+ * Duplicates are not allowed (by .isSameDeliverable comparison)
+ */
+public class DeliverableBook implements ReadOnlyDeliverableBook {
+ private final UniqueDeliverableList deliverables;
+
+ /*
+ * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication
+ * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html
+ *
+ * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication
+ * among constructors.
+ */
+ {
+ deliverables = new UniqueDeliverableList();
+ }
+
+ public DeliverableBook() {}
+
+ /**
+ * Creates an DeliverableBook using the Deliverables in the {@code toBeCopied}
+ */
+ public DeliverableBook(ReadOnlyDeliverableBook toBeCopied) {
+ this();
+ resetData(toBeCopied);
+ sortDeliverables();
+ }
+
+ /**
+ * Sorts the contents of the deliverable list by Deadline in chronological order.
+ */
+ public void sortDeliverables() {
+ this.deliverables.sortList();
+ }
+
+ //// list overwrite operations
+
+ /**
+ * Replaces the contents of the deliverable list with {@code deliverables}.
+ * {@code deliverables} must not contain duplicate deliverables.
+ */
+ public void setDeliverables(List deliverables) {
+ this.deliverables.setDeliverables(deliverables);
+ }
+
+ /**
+ * Resets the existing data of this {@code DeliverableBook} with {@code newData}.
+ */
+ public void resetData(ReadOnlyDeliverableBook newData) {
+ requireNonNull(newData);
+ setDeliverables(newData.getDeliverableList());
+ }
+
+ //// deliverable-level operations
+
+ /**
+ * Returns true if a deliverable with the same identity as {@code deliverable} exists in the deliverable book.
+ */
+ public boolean hasDeliverable(Deliverable deliverable) {
+ requireNonNull(deliverable);
+ return deliverables.contains(deliverable);
+ }
+
+ /**
+ * Adds a deliverable to the deliverable book.
+ * The deliverable must not already exist in the deliverable book.
+ */
+ public void addDeliverable(Deliverable p) {
+ deliverables.add(p);
+ }
+
+ /**
+ * Replaces the given deliverable {@code target} in the list with {@code editedDeliverable}.
+ * {@code target} must exist in the deliverable book.
+ * The deliverable identity of {@code editedDeliverable} must not be the same as
+ * another existing deliverable in the deliverable book.
+ */
+ public void setDeliverable(Deliverable target, Deliverable editedDeliverable) {
+ requireNonNull(editedDeliverable);
+ deliverables.setDeliverable(target, editedDeliverable);
+ }
+
+ /**
+ * Removes {@code key} from this {@code DeliverableBook}.
+ * {@code key} must exist in the deliverable book.
+ */
+ public void removeDeliverable(Deliverable key) {
+ deliverables.remove(key);
+ }
+
+ @Override
+ public String toString() {
+ return deliverables.asUnmodifiableObservableList().size() + " deliverables";
+ // TODO: refine later
+ }
+
+ @Override
+ public ObservableList getDeliverableList() {
+ return deliverables.asUnmodifiableObservableList();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DeliverableBook // instanceof handles nulls
+ && deliverables.equals(((DeliverableBook) other).deliverables));
+ }
+
+ @Override
+ public int hashCode() {
+ return deliverables.hashCode();
+ }
+
+ public ObservableList getInternalDeliverableList() {
+ return deliverables.getInternalDeliverableList();
+ }
+}
+
diff --git a/src/main/java/seedu/address/model/deliverable/ModelDeliverable.java b/src/main/java/seedu/address/model/deliverable/ModelDeliverable.java
new file mode 100644
index 00000000000..f2e5834f5db
--- /dev/null
+++ b/src/main/java/seedu/address/model/deliverable/ModelDeliverable.java
@@ -0,0 +1,106 @@
+package seedu.address.model.deliverable;
+
+import java.nio.file.Path;
+import java.util.function.Predicate;
+
+import javafx.collections.ObservableList;
+import seedu.address.commons.core.GuiSettings;
+import seedu.address.model.ReadOnlyUserPrefs;
+import seedu.address.model.deliverable.deliverable.Deliverable;
+
+/**
+ * Api of Model component of Deliverable
+ */
+public interface ModelDeliverable {
+ /** {@code Predicate} that always evaluate to true */
+ Predicate PREDICATE_SHOW_ALL_DELIVERABLES = unused -> true;
+
+ /**
+ * Replaces user prefs data with the data in {@code userPrefs}.
+ */
+ void setUserPrefs(ReadOnlyUserPrefs userPrefs);
+
+ /**
+ * Returns the user prefs.
+ */
+ ReadOnlyUserPrefs getUserPrefs();
+
+ /**
+ * Returns the user prefs' GUI settings.
+ */
+ GuiSettings getGuiSettings();
+
+ /**
+ * Sets the user prefs' GUI settings.
+ */
+ void setGuiSettings(GuiSettings guiSettings);
+
+ /**
+ * Returns the user prefs' deliverable book file path.
+ */
+ Path getDeliverableBookFilePath();
+
+ /**
+ * Sets the user prefs' deliverable book file path.
+ */
+ void setDeliverableBookFilePath(Path deliverableBookFilePath);
+
+ /**
+ * Replaces deliverable book data with the data in {@code deliverableBook}.
+ */
+ void setDeliverableBook(ReadOnlyDeliverableBook deliverableBook);
+
+ /** Returns the DeliverableBook */
+ ReadOnlyDeliverableBook getDeliverableBook();
+
+ /**
+ * Returns true if a deliverable with the same identity as {@code deliverable} exists in the deliverable book.
+ */
+ boolean hasDeliverable(Deliverable deliverable);
+
+ /**
+ * Deletes the given deliverable.
+ * The deliverable must exist in the deliverable book.
+ */
+ void deleteDeliverable(Deliverable target);
+
+ /**
+ * Adds the given deliverable.
+ * {@code deliverable} must not already exist in the deliverable book.
+ */
+ void addDeliverable(Deliverable deliverable);
+
+ /**
+ * Replaces the given deliverable {@code target} with {@code editedDeliverable}.
+ * {@code target} must exist in the deliverable book.
+ * The deliverable identity of {@code editedDeliverable} must not be the same as
+ * another existing deliverable in the deliverable book.
+ */
+ void setDeliverable(Deliverable target, Deliverable editedDeliverable);
+
+ /**
+ * Updates the completion status of given deliverable {@code target}
+ * by replacing it with {@code updatedDeliverable}.
+ * {@code target} must exist in the deliverable book.
+ */
+ void updateDeliverableStatus(Deliverable target, Deliverable updatedDeliverable);
+
+ /**
+ * Obtains the deliverable currently being viewed.
+ */
+ Deliverable getDeliverableInView();
+
+ void setDeliverableInView(Deliverable deliverableInView);
+
+ /** Returns an unmodifiable view of the filtered deliverable list */
+ ObservableList getFilteredDeliverableList();
+
+ /**
+ * Updates the filter of the filtered deliverable list to filter by the given {@code predicate}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredDeliverableList(Predicate predicate);
+
+ /** Returns the internal list of deliverables */
+ ObservableList getInternalDeliverableList();
+}
diff --git a/src/main/java/seedu/address/model/deliverable/ModelDeliverableManager.java b/src/main/java/seedu/address/model/deliverable/ModelDeliverableManager.java
new file mode 100644
index 00000000000..dd0eb453980
--- /dev/null
+++ b/src/main/java/seedu/address/model/deliverable/ModelDeliverableManager.java
@@ -0,0 +1,178 @@
+package seedu.address.model.deliverable;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.nio.file.Path;
+import java.util.function.Predicate;
+import java.util.logging.Logger;
+
+import javafx.collections.ObservableList;
+import javafx.collections.transformation.FilteredList;
+import seedu.address.commons.core.GuiSettings;
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.model.ReadOnlyUserPrefs;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.deliverable.deliverable.Deliverable;
+
+/**
+ * Represents the in-memory model of the deliverable book data
+ */
+public class ModelDeliverableManager implements ModelDeliverable {
+
+ private static final Logger logger = LogsCenter.getLogger(ModelDeliverableManager.class);
+ private final DeliverableBook deliverableBook;
+ private final UserPrefs userPrefs;
+ private final FilteredList filteredDeliverables;
+ private Deliverable deliverableInView;
+
+ /**
+ * Initializes a ModelDeliverableManager with the given deliverableBook and userPrefs.
+ */
+ public ModelDeliverableManager(ReadOnlyDeliverableBook deliverableBook, ReadOnlyUserPrefs userPrefs) {
+ super();
+ requireAllNonNull(deliverableBook, userPrefs);
+
+ logger.fine("Initializing with deliverable book: " + deliverableBook + " and user prefs " + userPrefs);
+
+ this.deliverableBook = new DeliverableBook(deliverableBook);
+ this.userPrefs = new UserPrefs(userPrefs);
+ filteredDeliverables = new FilteredList<>(this.deliverableBook.getDeliverableList());
+ }
+
+ public ModelDeliverableManager() {
+ this(new DeliverableBook(), new UserPrefs());
+ }
+
+ //=========== UserPrefs ==================================================================================
+
+ @Override
+ public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
+ requireNonNull(userPrefs);
+ this.userPrefs.resetData(userPrefs);
+ }
+
+ @Override
+ public ReadOnlyUserPrefs getUserPrefs() {
+ return userPrefs;
+ }
+
+ @Override
+ public GuiSettings getGuiSettings() {
+ return userPrefs.getGuiSettings();
+ }
+
+ @Override
+ public void setGuiSettings(GuiSettings guiSettings) {
+ requireNonNull(guiSettings);
+ userPrefs.setGuiSettings(guiSettings);
+ }
+
+ @Override
+ public Path getDeliverableBookFilePath() {
+ return userPrefs.getDeliverableBookFilePath();
+ }
+
+ @Override
+ public void setDeliverableBookFilePath(Path deliverableBookFilePath) {
+ requireNonNull(deliverableBookFilePath);
+ userPrefs.setDeliverableBookFilePath(deliverableBookFilePath);
+ }
+
+ //=========== DeliverableBook ================================================================================
+
+ @Override
+ public void setDeliverableBook(ReadOnlyDeliverableBook deliverableBook) {
+ this.deliverableBook.resetData(deliverableBook);
+ setDeliverableInView(null);
+ }
+
+ @Override
+ public ReadOnlyDeliverableBook getDeliverableBook() {
+ return deliverableBook;
+ }
+
+ @Override
+ public ObservableList getInternalDeliverableList() {
+ return deliverableBook.getInternalDeliverableList();
+ }
+
+ @Override
+ public boolean hasDeliverable(Deliverable deliverable) {
+ requireNonNull(deliverable);
+ return deliverableBook.hasDeliverable(deliverable);
+ }
+
+ @Override
+ public void deleteDeliverable(Deliverable target) {
+ if (target.isSameDeliverable(deliverableInView)) {
+ setDeliverableInView(null);
+ }
+ deliverableBook.removeDeliverable(target);
+ }
+
+ @Override
+ public void addDeliverable(Deliverable deliverable) {
+ deliverableBook.addDeliverable(deliverable);
+ updateFilteredDeliverableList(PREDICATE_SHOW_ALL_DELIVERABLES);
+ setDeliverableInView(deliverable);
+ }
+
+ @Override
+ public void setDeliverable(Deliverable target, Deliverable editedDeliverable) {
+ requireAllNonNull(target, editedDeliverable);
+ deliverableBook.setDeliverable(target, editedDeliverable);
+ updateFilteredDeliverableList(PREDICATE_SHOW_ALL_DELIVERABLES);
+ setDeliverableInView(editedDeliverable);
+ }
+
+ @Override
+ public void updateDeliverableStatus(Deliverable target, Deliverable updatedDeliverable) {
+ requireAllNonNull(target, updatedDeliverable);
+ deliverableBook.setDeliverable(target, updatedDeliverable);
+ setDeliverableInView(updatedDeliverable);
+ }
+
+ @Override
+ public Deliverable getDeliverableInView() {
+ return deliverableInView;
+ }
+
+ @Override
+ public void setDeliverableInView(Deliverable deliverableInView) {
+ this.deliverableInView = deliverableInView;
+ }
+
+ //=========== Filtered Deliverable List Accessors =============================================================
+
+ @Override
+ public ObservableList getFilteredDeliverableList() {
+ return filteredDeliverables;
+ }
+
+ @Override
+ public void updateFilteredDeliverableList(Predicate predicate) {
+ requireNonNull(predicate);
+ filteredDeliverables.setPredicate(predicate);
+ setDeliverableInView(null);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ // short circuit if same object
+ if (obj == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(obj instanceof ModelDeliverableManager)) {
+ return false;
+ }
+
+ // state check
+ ModelDeliverableManager other = (ModelDeliverableManager) obj;
+ return deliverableBook.equals(other.deliverableBook)
+ && userPrefs.equals(other.userPrefs)
+ && filteredDeliverables.equals(other.filteredDeliverables);
+ }
+}
diff --git a/src/main/java/seedu/address/model/deliverable/ReadOnlyDeliverableBook.java b/src/main/java/seedu/address/model/deliverable/ReadOnlyDeliverableBook.java
new file mode 100644
index 00000000000..d035cc221c5
--- /dev/null
+++ b/src/main/java/seedu/address/model/deliverable/ReadOnlyDeliverableBook.java
@@ -0,0 +1,15 @@
+package seedu.address.model.deliverable;
+
+import javafx.collections.ObservableList;
+import seedu.address.model.deliverable.deliverable.Deliverable;
+
+/**
+ * Unmodifiable view of an deliverable book
+ */
+public interface ReadOnlyDeliverableBook {
+ /**
+ * Returns an unmodifiable view of the deliverables list.
+ * This list will not contain any duplicate deliverables.
+ */
+ ObservableList getDeliverableList();
+}
diff --git a/src/main/java/seedu/address/model/deliverable/deliverable/Deadline.java b/src/main/java/seedu/address/model/deliverable/deliverable/Deadline.java
new file mode 100644
index 00000000000..0932f608beb
--- /dev/null
+++ b/src/main/java/seedu/address/model/deliverable/deliverable/Deadline.java
@@ -0,0 +1,39 @@
+package seedu.address.model.deliverable.deliverable;
+
+import seedu.address.model.util.DateTime;
+
+/**
+ * Represents a Deliverable's deadline in the deliverable book.
+ */
+public class Deadline extends DateTime {
+
+ public static final String MESSAGE_CONSTRAINTS = String.format(CONSTRAINTS, "Deadline");
+
+ /**
+ * Constructs a {@code Deadline}.
+ *
+ * @param deadline A valid Deadline.
+ */
+ public Deadline(String deadline) {
+ super(deadline);
+ }
+
+ /**
+ * Returns true if a given string is a valid deadline.
+ */
+ public static boolean isValidDeadline(String test) {
+ return isValidDateTime(test);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this
+ || (other instanceof Deadline
+ && value.equals(((Deadline) other).value));
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+}
diff --git a/src/main/java/seedu/address/model/deliverable/deliverable/Deliverable.java b/src/main/java/seedu/address/model/deliverable/deliverable/Deliverable.java
new file mode 100644
index 00000000000..b8e0a3c9ef8
--- /dev/null
+++ b/src/main/java/seedu/address/model/deliverable/deliverable/Deliverable.java
@@ -0,0 +1,146 @@
+package seedu.address.model.deliverable.deliverable;
+
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.time.LocalDateTime;
+import java.util.Objects;
+
+import seedu.address.model.event.TimeEvent;
+import seedu.address.model.util.Contacts;
+import seedu.address.model.util.Description;
+import seedu.address.model.util.Title;
+
+/**
+ * Represents a Deliverable in the deliverable book.
+ * Guarantees: details are present and not null, field values are validated, immutable.
+ */
+public class Deliverable implements TimeEvent {
+
+ // Identity fields
+ private final Title title;
+
+ // Data fields
+ private final Milestone milestone;
+ private final Description description;
+ private final Deadline deadline;
+ private final boolean isComplete;
+ private final Contacts contacts;
+
+ /**
+ * Only title and milestone field must be present. Used when adding new deliverable.
+ */
+ public Deliverable(Title title, Milestone milestone, Description description,
+ Deadline deadline, Contacts contacts) {
+ requireAllNonNull(title, milestone);
+ this.title = title;
+ this.milestone = milestone;
+ this.description = description;
+ this.deadline = deadline;
+ this.isComplete = false;
+ this.contacts = contacts;
+ }
+
+ /**
+ * Used when editing or completing existing deliverable.
+ */
+ public Deliverable(Title title, Milestone milestone, Description description, Deadline deadline,
+ boolean isComplete, Contacts contacts) {
+ requireAllNonNull(title);
+ this.title = title;
+ this.milestone = milestone;
+ this.description = description;
+ this.deadline = deadline;
+ this.isComplete = isComplete;
+ this.contacts = contacts;
+ }
+
+ public Title getTitle() {
+ return title;
+ }
+
+ public Milestone getMilestone() {
+ return milestone;
+ }
+
+ public Description getDescription() {
+ return description;
+ }
+
+ public Deadline getDeadline() {
+ return deadline;
+ }
+
+ public boolean getIsComplete() {
+ return isComplete;
+ }
+
+ public Contacts getContacts() {
+ return contacts;
+ }
+
+ @Override
+ public LocalDateTime getIndicatorTime() {
+ return deadline.getLocalDateTime();
+ }
+
+ /**
+ * Returns true if both deliverables share the same title.
+ * This defines a weaker notion of equality between two deliverables.
+ */
+ public boolean isSameDeliverable(Deliverable otherDeliverable) {
+ if (otherDeliverable == this) {
+ return true;
+ }
+
+ return otherDeliverable != null
+ && otherDeliverable.getTitle().equals(getTitle())
+ && otherDeliverable.getDeadline().equals(getDeadline());
+ }
+
+ /**
+ * Returns true if both deliverables have the same identity and data fields.
+ * This defines a stronger notion of equality between two deliverables.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof Deliverable)) {
+ return false;
+ }
+
+ Deliverable otherDeliverable = (Deliverable) other;
+ return otherDeliverable.getTitle().equals(getTitle())
+ && otherDeliverable.getMilestone().equals(getMilestone())
+ && otherDeliverable.getDescription().equals(getDescription())
+ && otherDeliverable.getDeadline().equals(getDeadline())
+ && otherDeliverable.getContacts().equals(getContacts())
+ && otherDeliverable.getIsComplete() == getIsComplete();
+ }
+
+ @Override
+ public int hashCode() {
+ // use this method for custom fields hashing instead of implementing your own
+ return Objects.hash(title, milestone, description, deadline, contacts, isComplete);
+
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Title: ")
+ .append(getTitle())
+ .append(" Deadline: ")
+ .append(getDeadline())
+ .append(" Milestone: ")
+ .append(getMilestone())
+ .append(" Contact(s): ")
+ .append(getContacts())
+ .append(" Description: ")
+ .append(getDescription());
+ return builder.toString();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/deliverable/deliverable/Milestone.java b/src/main/java/seedu/address/model/deliverable/deliverable/Milestone.java
new file mode 100644
index 00000000000..e77545456f3
--- /dev/null
+++ b/src/main/java/seedu/address/model/deliverable/deliverable/Milestone.java
@@ -0,0 +1,59 @@
+package seedu.address.model.deliverable.deliverable;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+/**
+ * Represents a deliverable's milestone in the deliverable book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidMilestone(String)}
+ */
+public class Milestone {
+
+ public static final String MESSAGE_CONSTRAINTS =
+ "Milestone should only take a non-negative integer, or a string of period-separated non-negative integers.";
+
+ /*
+ * Milestone can only take numerical values separated with dots.
+ * First and last characters must both be numbers.
+ */
+ public static final String VALIDATION_REGEX = "^\\d+(\\.\\d+)*$";
+
+ public static final String PREFIX = "v";
+
+ public final String value;
+
+ /**
+ * Constructs a {@code Milestone}.
+ *
+ * @param milestone A valid milestone.
+ */
+ public Milestone(String milestone) {
+ requireNonNull(milestone);
+ checkArgument(isValidMilestone(milestone), MESSAGE_CONSTRAINTS);
+ value = milestone;
+ }
+
+ /**
+ * Returns true if a given string is a valid milestone.
+ */
+ public static boolean isValidMilestone(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ @Override
+ public String toString() {
+ return PREFIX + value;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this
+ || (other instanceof Milestone)
+ && value.equals(((Milestone) other).value);
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+}
diff --git a/src/main/java/seedu/address/model/deliverable/deliverable/TitleDescriptionContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/deliverable/deliverable/TitleDescriptionContainsKeywordsPredicate.java
new file mode 100644
index 00000000000..6d245491aa4
--- /dev/null
+++ b/src/main/java/seedu/address/model/deliverable/deliverable/TitleDescriptionContainsKeywordsPredicate.java
@@ -0,0 +1,30 @@
+package seedu.address.model.deliverable.deliverable;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+import seedu.address.commons.util.StringUtil;
+
+public class TitleDescriptionContainsKeywordsPredicate implements Predicate {
+ private final List keywords;
+
+ public TitleDescriptionContainsKeywordsPredicate(List keywords) {
+ this.keywords = keywords;
+ }
+
+ @Override
+ public boolean test(Deliverable deliverable) {
+ return keywords.stream()
+ .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(
+ deliverable.getTitle().value.replaceAll("\\W", " "), keyword)
+ || StringUtil.containsWordIgnoreCase(
+ deliverable.getDescription().value.orElse("").replaceAll("\\W", " "), keyword));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof TitleDescriptionContainsKeywordsPredicate // instanceof handles nulls
+ && keywords.equals(((TitleDescriptionContainsKeywordsPredicate) other).keywords)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/model/deliverable/deliverable/UniqueDeliverableList.java b/src/main/java/seedu/address/model/deliverable/deliverable/UniqueDeliverableList.java
new file mode 100644
index 00000000000..c3ebd49431a
--- /dev/null
+++ b/src/main/java/seedu/address/model/deliverable/deliverable/UniqueDeliverableList.java
@@ -0,0 +1,146 @@
+package seedu.address.model.deliverable.deliverable;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import seedu.address.model.deliverable.deliverable.exceptions.DeliverableNotFoundException;
+import seedu.address.model.deliverable.deliverable.exceptions.DuplicateDeliverableException;
+import seedu.address.model.util.TimeEventComparator;
+
+public class UniqueDeliverableList implements Iterable {
+
+ private final ObservableList internalList = FXCollections.observableArrayList();
+ private final ObservableList internalUnmodifiableList =
+ FXCollections.unmodifiableObservableList(internalList);
+ private final TimeEventComparator timeEventComparator = new TimeEventComparator();
+
+ /**
+ * Returns true if the list contains an equivalent deliverable as the given argument.
+ */
+ public boolean contains(Deliverable toCheck) {
+ requireNonNull(toCheck);
+ return internalList.stream().anyMatch(toCheck::isSameDeliverable);
+ }
+
+ /**
+ * Adds a deliverable to the list.
+ * The deliverable must not already exist in the list.
+ */
+ public void add(Deliverable toAdd) {
+ requireNonNull(toAdd);
+ if (contains(toAdd)) {
+ throw new DuplicateDeliverableException();
+ }
+ internalList.add(toAdd);
+ sortList();
+ }
+
+ /**
+ * Replaces the deliverable {@code target} in the list with {@code editedDeliverable}.
+ * {@code target} must exist in the list.
+ * The deliverable identity of {@code editedDeliverable} must not be the same as
+ * another existing deliverable in the list.
+ */
+ public void setDeliverable(Deliverable target, Deliverable editedDeliverable) {
+ requireAllNonNull(target, editedDeliverable);
+
+ int index = internalList.indexOf(target);
+ if (index == -1) {
+ throw new DeliverableNotFoundException();
+ }
+
+ if (!target.isSameDeliverable(editedDeliverable) && contains(editedDeliverable)) {
+ throw new DuplicateDeliverableException();
+ }
+
+ internalList.set(index, editedDeliverable);
+ sortList();
+ }
+
+ /**
+ * Removes the equivalent deliverable from the list.
+ * The deliverable must exist in the list.
+ */
+ public void remove(Deliverable toRemove) {
+ requireNonNull(toRemove);
+ if (!internalList.remove(toRemove)) {
+ throw new DeliverableNotFoundException();
+ }
+ }
+
+ public void setDeliverables(UniqueDeliverableList replacement) {
+ requireNonNull(replacement);
+ internalList.setAll(replacement.internalList);
+ }
+
+ /**
+ * Replaces the contents of this list with {@code deliverables}.
+ * {@code deliverables} must not contain duplicate deliverables.
+ */
+ public void setDeliverables(List deliverables) {
+ requireAllNonNull(deliverables);
+ if (!deliverablesAreUnique(deliverables)) {
+ throw new DuplicateDeliverableException();
+ }
+
+ internalList.setAll(deliverables);
+ sortList();
+ }
+
+ /**
+ * Returns the backing list as an unmodifiable {@code ObservableList}.
+ */
+ public ObservableList asUnmodifiableObservableList() {
+ return internalUnmodifiableList;
+ }
+
+ /**
+ * Sort the list chronologically according to Deadline.
+ */
+ public void sortList() {
+ Collections.sort(internalList, timeEventComparator);
+ }
+
+ @Override
+ public Iterator iterator() {
+ sortList();
+ return internalList.iterator();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof UniqueDeliverableList // instanceof handles nulls
+ && internalList.equals(((UniqueDeliverableList) other).internalList));
+ }
+
+ @Override
+ public int hashCode() {
+ return internalList.hashCode();
+ }
+
+ /**
+ * Returns true if {@code deliverables} contains only unique deliverables.
+ */
+ private boolean deliverablesAreUnique(List deliverables) {
+ for (int i = 0; i < deliverables.size() - 1; i++) {
+ for (int j = i + 1; j < deliverables.size(); j++) {
+ if (deliverables.get(i).isSameDeliverable(deliverables.get(j))) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /** Returns the internal list of deliverables */
+ public ObservableList getInternalDeliverableList() {
+ return internalList;
+ }
+}
diff --git a/src/main/java/seedu/address/model/deliverable/deliverable/exceptions/DeliverableNotFoundException.java b/src/main/java/seedu/address/model/deliverable/deliverable/exceptions/DeliverableNotFoundException.java
new file mode 100644
index 00000000000..7d281d8639b
--- /dev/null
+++ b/src/main/java/seedu/address/model/deliverable/deliverable/exceptions/DeliverableNotFoundException.java
@@ -0,0 +1,7 @@
+package seedu.address.model.deliverable.deliverable.exceptions;
+
+/**
+ * Signals that the operation is unable to find the specified deliverable.
+ */
+public class DeliverableNotFoundException extends RuntimeException {
+}
diff --git a/src/main/java/seedu/address/model/deliverable/deliverable/exceptions/DuplicateDeliverableException.java b/src/main/java/seedu/address/model/deliverable/deliverable/exceptions/DuplicateDeliverableException.java
new file mode 100644
index 00000000000..0333c145d36
--- /dev/null
+++ b/src/main/java/seedu/address/model/deliverable/deliverable/exceptions/DuplicateDeliverableException.java
@@ -0,0 +1,11 @@
+package seedu.address.model.deliverable.deliverable.exceptions;
+
+/**
+ * Signals that the operation will result in duplicate Deliverables
+ * (Deliverables are considered duplicates if they have the same identity).
+ */
+public class DuplicateDeliverableException extends RuntimeException {
+ public DuplicateDeliverableException() {
+ super("Operation would result in duplicate deliverables");
+ }
+}
diff --git a/src/main/java/seedu/address/model/event/TimeEvent.java b/src/main/java/seedu/address/model/event/TimeEvent.java
new file mode 100644
index 00000000000..6a97223845e
--- /dev/null
+++ b/src/main/java/seedu/address/model/event/TimeEvent.java
@@ -0,0 +1,7 @@
+package seedu.address.model.event;
+
+import java.time.LocalDateTime;
+
+public interface TimeEvent {
+ LocalDateTime getIndicatorTime();
+}
diff --git a/src/main/java/seedu/address/model/meeting/MeetingBook.java b/src/main/java/seedu/address/model/meeting/MeetingBook.java
new file mode 100644
index 00000000000..9ba931a8fd0
--- /dev/null
+++ b/src/main/java/seedu/address/model/meeting/MeetingBook.java
@@ -0,0 +1,117 @@
+package seedu.address.model.meeting;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import javafx.collections.ObservableList;
+import seedu.address.model.meeting.meeting.Meeting;
+import seedu.address.model.meeting.meeting.UniqueMeetingList;
+
+/**
+ * Wraps all data at the meeting-book level
+ * Duplicates are not allowed (by .isSameMeeting comparison)
+ */
+public class MeetingBook implements ReadOnlyMeetingBook {
+ private final UniqueMeetingList meetings;
+
+ {
+ meetings = new UniqueMeetingList();
+ }
+
+ public MeetingBook() {}
+
+ /**
+ * Creates an MeetingBook using the Meetings in the {@code toBeCopied}
+ */
+ public MeetingBook(ReadOnlyMeetingBook toBeCopied) {
+ this();
+ resetData(toBeCopied);
+ sortMeetings();
+ }
+
+ /**
+ * Sorts the contents of the meeting list by From in chronological order.
+ */
+ public void sortMeetings() {
+ this.meetings.sortList();
+ }
+
+ /**
+ * Replaces the contents of the meeting list with {@code meetings}.
+ * {@code meetings} must not contain duplicate meetings.
+ */
+ public void setMeetings(List meetings) {
+ this.meetings.setMeetings(meetings);
+ }
+
+ /**
+ * Resets the existing data of this {@code MeetingBook} with {@code newData}.
+ */
+ public void resetData(ReadOnlyMeetingBook newData) {
+ requireNonNull(newData);
+
+ setMeetings(newData.getMeetingList());
+ }
+
+ /**
+ * Adds a meeting to the meeting book.
+ * The meeting must not already exist in the meeting book.
+ */
+ public void addMeeting(Meeting m) {
+ meetings.add(m);
+ }
+
+ /**
+ * Returns true if a meeting with the same identity as {@code meeting} exists in the meeting book.
+ */
+ public boolean hasMeeting(Meeting meeting) {
+ requireNonNull(meeting);
+ return meetings.contains(meeting);
+ }
+
+ /**
+ * Replaces the given meeting {@code target} in the list with {@code editedMeeting}.
+ * {@code target} must exist in the meeting book.
+ * The meeting identity of {@code editedMeeting} must not be the same as
+ * another existing meeting in the meeting book.
+ */
+ public void setMeeting(Meeting target, Meeting editedMeeting) {
+ requireNonNull(editedMeeting);
+ meetings.setMeeting(target, editedMeeting);
+ }
+
+ /**
+ * Removes {@code key} from this {@code MeetingBook}.
+ * {@code key} must exist in the meeting book.
+ */
+ public void removeMeeting(Meeting key) {
+ meetings.remove(key);
+ }
+
+ @Override
+ public String toString() {
+ return meetings.asUnmodifiableObservableList().size() + " meetings";
+ }
+
+ @Override
+ public ObservableList getMeetingList() {
+ return meetings.asUnmodifiableObservableList();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this
+ || (other instanceof MeetingBook
+ && meetings.equals(((MeetingBook) other).meetings));
+ }
+
+ @Override
+ public int hashCode() {
+ return meetings.hashCode();
+ }
+
+ public ObservableList getInternalMeetingList() {
+ return meetings.getInternalMeetingList();
+ }
+}
diff --git a/src/main/java/seedu/address/model/meeting/ModelMeeting.java b/src/main/java/seedu/address/model/meeting/ModelMeeting.java
new file mode 100644
index 00000000000..d127ad26e3f
--- /dev/null
+++ b/src/main/java/seedu/address/model/meeting/ModelMeeting.java
@@ -0,0 +1,97 @@
+package seedu.address.model.meeting;
+
+import java.nio.file.Path;
+import java.util.function.Predicate;
+
+import javafx.collections.ObservableList;
+import seedu.address.commons.core.GuiSettings;
+import seedu.address.model.ReadOnlyUserPrefs;
+import seedu.address.model.meeting.meeting.Meeting;
+
+/**
+ * The API of the Model component of Meeting.
+ */
+public interface ModelMeeting {
+ Predicate PREDICATE_SHOW_ALL_MEETINGS = unused -> true;
+
+ /**
+ * Replaces user prefs data with the data in {@code userPrefs}.
+ */
+ void setUserPrefs(ReadOnlyUserPrefs userPrefs);
+
+ /**
+ * Returns the user prefs.
+ */
+ ReadOnlyUserPrefs getUserPrefs();
+
+ /**
+ * Returns the user prefs' GUI settings.
+ */
+ GuiSettings getGuiSettings();
+
+ /**
+ * Sets the user prefs' GUI settings.
+ */
+ void setGuiSettings(GuiSettings guiSettings);
+
+ /**
+ * Returns the user prefs' deliverable book file path.
+ */
+ Path getMeetingBookFilePath();
+
+ /**
+ * Sets the user prefs' deliverable book file path.
+ */
+ void setMeetingBookFilePath(Path deliverableBookFilePath);
+
+ /**
+ * Replaces deliverable book data with the data in {@code deliverableBook}.
+ */
+ void setMeetingBook(ReadOnlyMeetingBook deliverableBook);
+
+ /** Returns the MeetingBook */
+ ReadOnlyMeetingBook getMeetingBook();
+
+ /** Returns the meeting currently in view. */
+ Meeting getMeetingInView();
+
+ /** Updates the meeting currently in view */
+ void setMeetingInView(Meeting meetingInView);
+
+ /**
+ * Returns true if a deliverable with the same identity as {@code deliverable} exists in the deliverable book.
+ */
+ boolean hasMeeting(Meeting deliverable);
+
+ /**
+ * Deletes the given deliverable.
+ * The deliverable must exist in the deliverable book.
+ */
+ void deleteMeeting(Meeting target);
+
+ /**
+ * Adds the given deliverable.
+ * {@code deliverable} must not already exist in the deliverable book.
+ */
+ void addMeeting(Meeting deliverable);
+
+ /**
+ * Replaces the given deliverable {@code target} with {@code editedMeeting}.
+ * {@code target} must exist in the deliverable book.
+ * The deliverable identity of {@code editedMeeting} must not be the same as
+ * another existing deliverable in the deliverable book.
+ */
+ void setMeeting(Meeting target, Meeting editedMeeting);
+
+ /** Returns an unmodifiable view of the filtered deliverable list */
+ ObservableList getFilteredMeetingList();
+
+ /** Returns the internal meeting list */
+ ObservableList getInternalMeetingList();
+
+ /**
+ * Updates the filter of the filtered deliverable list to filter by the given {@code predicate}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredMeetingList(Predicate predicate);
+}
diff --git a/src/main/java/seedu/address/model/meeting/ModelMeetingManager.java b/src/main/java/seedu/address/model/meeting/ModelMeetingManager.java
new file mode 100644
index 00000000000..8c1552a7494
--- /dev/null
+++ b/src/main/java/seedu/address/model/meeting/ModelMeetingManager.java
@@ -0,0 +1,166 @@
+package seedu.address.model.meeting;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.nio.file.Path;
+import java.util.function.Predicate;
+import java.util.logging.Logger;
+
+import javafx.collections.ObservableList;
+import javafx.collections.transformation.FilteredList;
+import seedu.address.commons.core.GuiSettings;
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.model.ReadOnlyUserPrefs;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.meeting.meeting.Meeting;
+
+/**
+ * Represents the in-memory model of the meeting book data
+ */
+public class ModelMeetingManager implements ModelMeeting {
+
+ private static final Logger logger = LogsCenter.getLogger(ModelMeetingManager.class);
+ private final MeetingBook meetingBook;
+ private final UserPrefs userPrefs;
+ private final FilteredList filteredMeetings;
+ private Meeting meetingInView;
+
+ /**
+ * Initializes a ModelMeetingManager with the given meetingBook WITHOUT userPrefs.
+ */
+ public ModelMeetingManager(ReadOnlyMeetingBook meetingBook, ReadOnlyUserPrefs userPrefs) {
+ super();
+ requireAllNonNull(meetingBook, userPrefs);
+
+ logger.fine("Initializing with meeting book: " + meetingBook + " and user prefs " + userPrefs);
+ this.meetingBook = new MeetingBook(meetingBook);
+ this.userPrefs = new UserPrefs(userPrefs);
+ filteredMeetings = new FilteredList<>(this.meetingBook.getMeetingList());
+ }
+
+ public ModelMeetingManager() {
+ this(new MeetingBook(), new UserPrefs());
+ }
+
+ //=========== UserPrefs ==================================================================================
+
+ @Override
+ public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
+ requireNonNull(userPrefs);
+ this.userPrefs.resetData(userPrefs);
+ }
+
+ @Override
+ public ReadOnlyUserPrefs getUserPrefs() {
+ return userPrefs;
+ }
+
+ @Override
+ public GuiSettings getGuiSettings() {
+ return userPrefs.getGuiSettings();
+ }
+
+ @Override
+ public void setGuiSettings(GuiSettings guiSettings) {
+ requireNonNull(guiSettings);
+ userPrefs.setGuiSettings(guiSettings);
+ }
+
+ @Override
+ public Path getMeetingBookFilePath() {
+ return userPrefs.getMeetingBookFilePath();
+ }
+
+ @Override
+ public void setMeetingBookFilePath(Path meetingBookFilePath) {
+ requireNonNull(meetingBookFilePath);
+ userPrefs.setMeetingBookFilePath(meetingBookFilePath);
+ }
+
+ @Override
+ public ObservableList getInternalMeetingList() {
+ return meetingBook.getInternalMeetingList();
+ }
+
+ //=========== MeetingBook ================================================================================
+
+ @Override
+ public void setMeetingBook(ReadOnlyMeetingBook meetingBook) {
+ this.meetingBook.resetData(meetingBook);
+ setMeetingInView(null);
+ }
+
+ @Override
+ public ReadOnlyMeetingBook getMeetingBook() {
+ return meetingBook;
+ }
+
+ @Override
+ public Meeting getMeetingInView() {
+ return meetingInView;
+ }
+
+ @Override
+ public void setMeetingInView(Meeting meetingInView) {
+ this.meetingInView = meetingInView;
+ }
+
+ @Override
+ public void addMeeting(Meeting meeting) {
+ meetingBook.addMeeting(meeting);
+ updateFilteredMeetingList(PREDICATE_SHOW_ALL_MEETINGS);
+ setMeetingInView(meeting);
+ }
+
+ @Override
+ public void deleteMeeting(Meeting target) {
+ meetingBook.removeMeeting(target);
+ if (target.isSameMeeting(meetingInView)) {
+ setMeetingInView(null);
+ }
+ }
+
+ @Override
+ public boolean hasMeeting(Meeting meeting) {
+ requireNonNull(meeting);
+ return meetingBook.hasMeeting(meeting);
+ }
+
+ @Override
+ public void setMeeting(Meeting target, Meeting editedMeeting) {
+ requireAllNonNull(target, editedMeeting);
+ meetingBook.setMeeting(target, editedMeeting);
+ updateFilteredMeetingList(PREDICATE_SHOW_ALL_MEETINGS);
+ setMeetingInView(editedMeeting);
+ }
+
+ //=========== Filtered Meeting List Accessors =============================================================
+ @Override
+ public ObservableList getFilteredMeetingList() {
+ return filteredMeetings;
+ }
+
+ @Override
+ public void updateFilteredMeetingList(Predicate predicate) {
+ requireNonNull(predicate);
+ filteredMeetings.setPredicate(predicate);
+ setMeetingInView(null);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+
+ if (!(obj instanceof ModelMeetingManager)) {
+ return false;
+ }
+
+ ModelMeetingManager other = (ModelMeetingManager) obj;
+ return meetingBook.equals(other.meetingBook)
+ && userPrefs.equals(other.userPrefs)
+ && filteredMeetings.equals(other.filteredMeetings);
+ }
+}
diff --git a/src/main/java/seedu/address/model/meeting/ReadOnlyMeetingBook.java b/src/main/java/seedu/address/model/meeting/ReadOnlyMeetingBook.java
new file mode 100644
index 00000000000..f3ede9d5be3
--- /dev/null
+++ b/src/main/java/seedu/address/model/meeting/ReadOnlyMeetingBook.java
@@ -0,0 +1,16 @@
+package seedu.address.model.meeting;
+
+import javafx.collections.ObservableList;
+import seedu.address.model.meeting.meeting.Meeting;
+
+/**
+ * Unmodifiable view of a meeting book
+ */
+
+public interface ReadOnlyMeetingBook {
+ /**
+ * Returns an unmodifiable view of the meeting list.
+ * This list will not contain any duplicate meeting.
+ */
+ ObservableList getMeetingList();
+}
diff --git a/src/main/java/seedu/address/model/meeting/meeting/From.java b/src/main/java/seedu/address/model/meeting/meeting/From.java
new file mode 100644
index 00000000000..73e8cec5ca4
--- /dev/null
+++ b/src/main/java/seedu/address/model/meeting/meeting/From.java
@@ -0,0 +1,35 @@
+package seedu.address.model.meeting.meeting;
+
+import seedu.address.model.util.DateTime;
+
+/**
+ * Represents a Meeting's from in the meeting book.
+ */
+public class From extends DateTime {
+
+ public static final String MESSAGE_CONSTRAINTS = String.format(CONSTRAINTS, "From");
+
+ /**
+ * Constructs a {@code From}.
+ *
+ * @param from A valid From.
+ */
+ public From(String from) {
+ super(from);
+ }
+
+ /**
+ * Returns true if a given string is a valid from.
+ */
+ public static boolean isValidFrom(String test) {
+ return isValidDateTime(test);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this
+ || (other instanceof From
+ && value.equals(((From) other).value));
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/meeting/meeting/Location.java b/src/main/java/seedu/address/model/meeting/meeting/Location.java
new file mode 100644
index 00000000000..d69cde1de80
--- /dev/null
+++ b/src/main/java/seedu/address/model/meeting/meeting/Location.java
@@ -0,0 +1,82 @@
+package seedu.address.model.meeting.meeting;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import java.util.Optional;
+
+/**
+ * Represents a Person's location in the location book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidLocation(String)}
+ */
+public class Location {
+ public static final String EMPTY_LOCATION_FIELD = "-";
+ public static final String MESSAGE_CONSTRAINTS = "Locations can take any value.";
+ public static final String VALIDATION_REGEX = "[^\\s].*";
+
+ /*
+ * Represents the value of Location.
+ */
+ public final Optional value;
+
+ /**
+ * Constructs an {@code Location}.
+ *
+ * @param location A valid location.
+ */
+ public Location(Optional location) {
+ if (location.isPresent()) {
+ checkArgument(isValidLocation(location.get()), MESSAGE_CONSTRAINTS);
+ value = location.get().isEmpty()
+ ? Optional.empty()
+ : location;
+ } else {
+ value = location;
+ }
+ }
+
+ /**
+ * Constructs an {@code Location}.
+ *
+ * @param location A valid location.
+ */
+ public Location(String location) {
+ requireNonNull(location);
+ checkArgument(isValidLocation(location), MESSAGE_CONSTRAINTS);
+ if (location.isEmpty()) {
+ value = Optional.empty();
+ } else {
+ value = Optional.of(location);
+ }
+
+ }
+
+ /**
+ * Returns true if a given string is a valid location.
+ */
+ public static boolean isValidLocation(String test) {
+ if (test.isEmpty()) {
+ return true;
+ } else {
+ return test.matches(VALIDATION_REGEX);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return value.orElse(EMPTY_LOCATION_FIELD);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this
+ || (other instanceof Location
+ && value.equals(((Location) other).value));
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/meeting/meeting/Meeting.java b/src/main/java/seedu/address/model/meeting/meeting/Meeting.java
new file mode 100644
index 00000000000..1df5c9a6498
--- /dev/null
+++ b/src/main/java/seedu/address/model/meeting/meeting/Meeting.java
@@ -0,0 +1,147 @@
+package seedu.address.model.meeting.meeting;
+
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.Objects;
+
+import seedu.address.model.event.TimeEvent;
+import seedu.address.model.util.Contacts;
+import seedu.address.model.util.Description;
+import seedu.address.model.util.Title;
+
+/**
+ * Represents a Meeting in the meeting book.
+ * Guarantees: details are present and not null, field values are validated, immutable.
+ */
+public class Meeting implements TimeEvent {
+
+ public static final String INCORRECT_FROM_AND_TO_ORDER = "From time should be earlier than To.";
+
+ private final Title title;
+ private final Description description;
+ private final From from;
+ private final To to;
+ private final Contacts contacts;
+ private final Location location;
+
+ /**
+ * Every field must be present and not null.
+ */
+ public Meeting(Title title, Description description, From from, To to,
+ Contacts contacts, Location location) throws IllegalArgumentException {
+
+ requireAllNonNull(title, description, from, to, contacts, location);
+
+ if (!isValidFromAndTo(from, to)) {
+ throw new IllegalArgumentException(INCORRECT_FROM_AND_TO_ORDER);
+ }
+
+ this.title = title;
+ this.description = description;
+ this.from = from;
+ this.to = to;
+ this.contacts = contacts;
+ this.location = location;
+ }
+
+ /**
+ * Returns true if From is earlier than To chronologically.
+ */
+ public static boolean isValidFromAndTo (From from, To to) {
+ LocalTime dateFrom = from.getLocalDateTime().toLocalTime();
+ LocalTime dateTo = to.getLocalTime();
+
+ return dateFrom.compareTo(dateTo) < 0;
+ }
+
+ public Title getTitle() {
+ return title;
+ }
+
+ public Description getDescription() {
+ return description;
+ }
+
+ public From getFrom() {
+ return from;
+ }
+
+ @Override
+ public LocalDateTime getIndicatorTime() {
+ return from.getLocalDateTime();
+ }
+
+ public To getTo() {
+ return to;
+ }
+
+ public Contacts getContacts() {
+ return contacts;
+ }
+
+ public Location getLocation() {
+ return location;
+ }
+
+ /**
+ * Returns true if both meetings have the same identity.
+ */
+ public boolean isSameMeeting(Meeting otherMeeting) {
+ if (otherMeeting == this) {
+ return true;
+ }
+
+ return otherMeeting != null
+ && otherMeeting.getTitle().equals(getTitle())
+ && otherMeeting.getFrom().equals(getFrom())
+ && otherMeeting.getTo().equals(getTo());
+ }
+
+ /**
+ * Returns true if both meetings have the same identity.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof Meeting)) {
+ return false;
+ }
+
+ Meeting otherMeeting = (Meeting) other;
+ return otherMeeting.getTitle().equals(getTitle())
+ && otherMeeting.getDescription().equals(getDescription())
+ && otherMeeting.getFrom().equals(getFrom())
+ && otherMeeting.getTo().equals(getTo())
+ && otherMeeting.getContacts().equals(getContacts())
+ && otherMeeting.getLocation().equals(getLocation());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(title, description, from, to, contacts, location);
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Title: ")
+ .append(getTitle())
+ .append(" From: ")
+ .append(getFrom())
+ .append(" To: ")
+ .append(getTo())
+ .append(" Contact(s): ")
+ .append(getContacts())
+ .append(" Location: ")
+ .append(getLocation())
+ .append(" Description: ")
+ .append(getDescription());
+ return builder.toString();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/meeting/meeting/TitleDescriptionContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/meeting/meeting/TitleDescriptionContainsKeywordsPredicate.java
new file mode 100644
index 00000000000..d360beccb95
--- /dev/null
+++ b/src/main/java/seedu/address/model/meeting/meeting/TitleDescriptionContainsKeywordsPredicate.java
@@ -0,0 +1,33 @@
+package seedu.address.model.meeting.meeting;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+import seedu.address.commons.util.StringUtil;
+
+/**
+ * Tests that a {@code Person}'s {@code Name} matches any of the keywords given.
+ */
+public class TitleDescriptionContainsKeywordsPredicate implements Predicate {
+ private final List keywords;
+
+ public TitleDescriptionContainsKeywordsPredicate(List keywords) {
+ this.keywords = keywords;
+ }
+
+ @Override
+ public boolean test(Meeting meeting) {
+ return keywords.stream()
+ .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(
+ meeting.getTitle().value.replaceAll("\\W", " "), keyword)
+ || StringUtil.containsWordIgnoreCase(
+ meeting.getDescription().value.orElse("").replaceAll("\\W", " "), keyword));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof TitleDescriptionContainsKeywordsPredicate // instanceof handles nulls
+ && keywords.equals(((TitleDescriptionContainsKeywordsPredicate) other).keywords)); // state check
+ }
+}
diff --git a/src/main/java/seedu/address/model/meeting/meeting/To.java b/src/main/java/seedu/address/model/meeting/meeting/To.java
new file mode 100644
index 00000000000..99ccfdaabb1
--- /dev/null
+++ b/src/main/java/seedu/address/model/meeting/meeting/To.java
@@ -0,0 +1,35 @@
+package seedu.address.model.meeting.meeting;
+
+import seedu.address.model.util.Time;
+
+/**
+ * Represents a Meeting's to in the meeting book.
+ */
+public class To extends Time {
+
+ public static final String MESSAGE_CONSTRAINTS = String.format(CONSTRAINTS, "To");
+
+ /**
+ * Constructs a {@code To}.
+ *
+ * @param to A valid To.
+ */
+ public To(String to) {
+ super(to);
+ }
+
+ /**
+ * Returns true if a given string is a valid To.
+ */
+ public static boolean isValidTo(String test) {
+ return isValidTime(test);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this
+ || (other instanceof To
+ && value.equals(((To) other).value));
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/meeting/meeting/UniqueMeetingList.java b/src/main/java/seedu/address/model/meeting/meeting/UniqueMeetingList.java
new file mode 100644
index 00000000000..273252f84a6
--- /dev/null
+++ b/src/main/java/seedu/address/model/meeting/meeting/UniqueMeetingList.java
@@ -0,0 +1,147 @@
+package seedu.address.model.meeting.meeting;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import seedu.address.model.meeting.meeting.exceptions.DuplicateMeetingException;
+import seedu.address.model.meeting.meeting.exceptions.MeetingNotFoundException;
+import seedu.address.model.util.TimeEventComparator;
+
+public class UniqueMeetingList implements Iterable {
+
+ private final ObservableList internalList = FXCollections.observableArrayList();
+ private final ObservableList internalUnmodifiableList =
+ FXCollections.unmodifiableObservableList(internalList);
+ private final TimeEventComparator timeEventComparator = new TimeEventComparator();
+
+ /**
+ * Returns true if the list contains an equivalent meeting as the given argument.
+ */
+ public boolean contains(Meeting toCheck) {
+ requireNonNull(toCheck);
+ return internalList.stream().anyMatch(toCheck::isSameMeeting);
+ }
+
+ /**
+ * Adds a meeting to the list.
+ * The meeting must not already exist in the list.
+ */
+ public void add(Meeting toAdd) {
+ requireNonNull(toAdd);
+ if (contains(toAdd)) {
+ throw new DuplicateMeetingException();
+ }
+ internalList.add(toAdd);
+ sortList();
+ }
+
+ /**
+ * Replaces the meeting {@code target} in the list with {@code editedMeeting}.
+ * {@code target} must exist in the list.
+ * The meeting identity of {@code editedMeeting} must not be the same as
+ * another existing meeting in the list.
+ */
+ public void setMeeting(Meeting target, Meeting editedMeeting) {
+ requireAllNonNull(target, editedMeeting);
+
+ int index = internalList.indexOf(target);
+ if (index == -1) {
+ throw new MeetingNotFoundException();
+ }
+
+ if (!target.isSameMeeting(editedMeeting) && contains(editedMeeting)) {
+ throw new DuplicateMeetingException();
+ }
+
+ internalList.set(index, editedMeeting);
+ sortList();
+ }
+
+ /**
+ * Removes the equivalent meeting from the list.
+ * The meeting must exist in the list.
+ */
+ public void remove(Meeting toRemove) {
+ requireNonNull(toRemove);
+ if (!internalList.remove(toRemove)) {
+ throw new MeetingNotFoundException();
+ }
+ }
+
+ public void setMeetings(UniqueMeetingList replacement) {
+ requireNonNull(replacement);
+ internalList.setAll(replacement.internalList);
+ sortList();
+ }
+
+ /**
+ * Replaces the contents of this list with {@code meetings}.
+ * {@code meetings} must not contain duplicate meetings.
+ */
+ public void setMeetings(List meetings) {
+ requireAllNonNull(meetings);
+ if (!meetingsAreUnique(meetings)) {
+ throw new DuplicateMeetingException();
+ }
+
+ internalList.setAll(meetings);
+ sortList();
+ }
+
+ /**
+ * Returns the backing list as an unmodifiable {@code ObservableList}.
+ */
+ public ObservableList asUnmodifiableObservableList() {
+ return internalUnmodifiableList;
+ }
+
+ /**
+ * Sort the list chronologically according to From.
+ */
+ public void sortList() {
+ Collections.sort(internalList, timeEventComparator);
+ }
+
+ @Override
+ public Iterator iterator() {
+ sortList();
+ return internalList.iterator();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof UniqueMeetingList // instanceof handles nulls
+ && internalList.equals(((UniqueMeetingList) other).internalList));
+ }
+
+ @Override
+ public int hashCode() {
+ return internalList.hashCode();
+ }
+
+ /**
+ * Returns true if {@code meetings} contains only unique meetings.
+ */
+ private boolean meetingsAreUnique(List meetings) {
+ for (int i = 0; i < meetings.size() - 1; i++) {
+ for (int j = i + 1; j < meetings.size(); j++) {
+ if (meetings.get(i).isSameMeeting(meetings.get(j))) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /** Returns the internal list of meeting */
+ public ObservableList getInternalMeetingList() {
+ return internalList;
+ }
+}
diff --git a/src/main/java/seedu/address/model/meeting/meeting/exceptions/DuplicateMeetingException.java b/src/main/java/seedu/address/model/meeting/meeting/exceptions/DuplicateMeetingException.java
new file mode 100644
index 00000000000..ee1919a6659
--- /dev/null
+++ b/src/main/java/seedu/address/model/meeting/meeting/exceptions/DuplicateMeetingException.java
@@ -0,0 +1,13 @@
+package seedu.address.model.meeting.meeting.exceptions;
+
+/**
+ * Signals that the operation will result in duplicate Meetings (Meetings are considered duplicates if
+ * they have the same information).
+ */
+public class DuplicateMeetingException extends RuntimeException {
+ public DuplicateMeetingException() {
+ super("Operation would result in duplicate meetings.");
+ }
+}
+
+
diff --git a/src/main/java/seedu/address/model/meeting/meeting/exceptions/MeetingNotFoundException.java b/src/main/java/seedu/address/model/meeting/meeting/exceptions/MeetingNotFoundException.java
new file mode 100644
index 00000000000..a4b8555fd84
--- /dev/null
+++ b/src/main/java/seedu/address/model/meeting/meeting/exceptions/MeetingNotFoundException.java
@@ -0,0 +1,7 @@
+package seedu.address.model.meeting.meeting.exceptions;
+
+/**
+ * Signals that the operation is unable to find the specified meeting.
+ */
+public class MeetingNotFoundException extends RuntimeException {
+}
diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java
deleted file mode 100644
index 60472ca22a0..00000000000
--- a/src/main/java/seedu/address/model/person/Address.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package seedu.address.model.person;
-
-import static java.util.Objects.requireNonNull;
-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)}
- */
-public class Address {
-
- public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, 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 = "[^\\s].*";
-
- public final String value;
-
- /**
- * Constructs an {@code Address}.
- *
- * @param address A valid address.
- */
- public Address(String address) {
- requireNonNull(address);
- checkArgument(isValidAddress(address), MESSAGE_CONSTRAINTS);
- value = address;
- }
-
- /**
- * Returns true if a given string is a valid email.
- */
- public static boolean isValidAddress(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 Address // instanceof handles nulls
- && value.equals(((Address) other).value)); // state check
- }
-
- @Override
- public int hashCode() {
- return value.hashCode();
- }
-
-}
diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/person/AddressBook.java
similarity index 87%
rename from src/main/java/seedu/address/model/AddressBook.java
rename to src/main/java/seedu/address/model/person/AddressBook.java
index 1a943a0781a..578330d5e00 100644
--- a/src/main/java/seedu/address/model/AddressBook.java
+++ b/src/main/java/seedu/address/model/person/AddressBook.java
@@ -1,12 +1,12 @@
-package seedu.address.model;
+package seedu.address.model.person;
import static java.util.Objects.requireNonNull;
import java.util.List;
import javafx.collections.ObservableList;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.UniquePersonList;
+import seedu.address.model.person.person.Person;
+import seedu.address.model.person.person.UniquePersonList;
/**
* Wraps all data at the address-book level
@@ -35,6 +35,14 @@ public AddressBook() {}
public AddressBook(ReadOnlyAddressBook toBeCopied) {
this();
resetData(toBeCopied);
+ sortContacts();
+ }
+
+ /**
+ * Sorts the contents of the contact list by Name in alphabetical order.
+ */
+ public void sortContacts() {
+ persons.sortList();
}
//// list overwrite operations
@@ -97,7 +105,7 @@ public void removePerson(Person key) {
@Override
public String toString() {
- return persons.asUnmodifiableObservableList().size() + " persons";
+ return persons.asUnmodifiableObservableList().size() + " contacts";
// TODO: refine later
}
@@ -106,6 +114,10 @@ public ObservableList getPersonList() {
return persons.asUnmodifiableObservableList();
}
+ public ObservableList getInternalPersonList() {
+ return persons.getInternalPersonList();
+ }
+
@Override
public boolean equals(Object other) {
return other == this // short circuit if same object
diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/person/ModelPerson.java
similarity index 84%
rename from src/main/java/seedu/address/model/Model.java
rename to src/main/java/seedu/address/model/person/ModelPerson.java
index d54df471c1f..76d57acd306 100644
--- a/src/main/java/seedu/address/model/Model.java
+++ b/src/main/java/seedu/address/model/person/ModelPerson.java
@@ -1,16 +1,17 @@
-package seedu.address.model;
+package seedu.address.model.person;
import java.nio.file.Path;
import java.util.function.Predicate;
import javafx.collections.ObservableList;
import seedu.address.commons.core.GuiSettings;
-import seedu.address.model.person.Person;
+import seedu.address.model.ReadOnlyUserPrefs;
+import seedu.address.model.person.person.Person;
/**
* The API of the Model component.
*/
-public interface Model {
+public interface ModelPerson {
/** {@code Predicate} that always evaluate to true */
Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true;
@@ -52,6 +53,12 @@ public interface Model {
/** Returns the AddressBook */
ReadOnlyAddressBook getAddressBook();
+ /** Returns the person currently in view. */
+ Person getPersonInView();
+
+ /** Updates the person in view. */
+ void setPersonInView(Person person);
+
/**
* Returns true if a person with the same identity as {@code person} exists in the address book.
*/
@@ -84,4 +91,7 @@ public interface Model {
* @throws NullPointerException if {@code predicate} is null.
*/
void updateFilteredPersonList(Predicate predicate);
+
+ /** Returns the internal list of deliverables */
+ ObservableList getInternalPersonList();
}
diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/person/ModelPersonManager.java
similarity index 76%
rename from src/main/java/seedu/address/model/ModelManager.java
rename to src/main/java/seedu/address/model/person/ModelPersonManager.java
index 0650c954f5c..0ccc7036e93 100644
--- a/src/main/java/seedu/address/model/ModelManager.java
+++ b/src/main/java/seedu/address/model/person/ModelPersonManager.java
@@ -1,4 +1,4 @@
-package seedu.address.model;
+package seedu.address.model.person;
import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
@@ -11,33 +11,36 @@
import javafx.collections.transformation.FilteredList;
import seedu.address.commons.core.GuiSettings;
import seedu.address.commons.core.LogsCenter;
-import seedu.address.model.person.Person;
+import seedu.address.model.ReadOnlyUserPrefs;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.person.person.Person;
/**
* Represents the in-memory model of the address book data.
*/
-public class ModelManager implements Model {
- private static final Logger logger = LogsCenter.getLogger(ModelManager.class);
+public class ModelPersonManager implements ModelPerson {
+ private static final Logger logger = LogsCenter.getLogger(ModelPersonManager.class);
private final AddressBook addressBook;
private final UserPrefs userPrefs;
private final FilteredList filteredPersons;
+ private Person personInView;
/**
* Initializes a ModelManager with the given addressBook and userPrefs.
*/
- public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) {
+ public ModelPersonManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) {
super();
requireAllNonNull(addressBook, userPrefs);
- logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs);
+ logger.fine("Initializing with contact book: " + addressBook + " and user prefs " + userPrefs);
this.addressBook = new AddressBook(addressBook);
this.userPrefs = new UserPrefs(userPrefs);
filteredPersons = new FilteredList<>(this.addressBook.getPersonList());
}
- public ModelManager() {
+ public ModelPersonManager() {
this(new AddressBook(), new UserPrefs());
}
@@ -81,6 +84,7 @@ public void setAddressBookFilePath(Path addressBookFilePath) {
@Override
public void setAddressBook(ReadOnlyAddressBook addressBook) {
this.addressBook.resetData(addressBook);
+ setPersonInView(null);
}
@Override
@@ -88,6 +92,21 @@ public ReadOnlyAddressBook getAddressBook() {
return addressBook;
}
+ @Override
+ public ObservableList getInternalPersonList() {
+ return addressBook.getInternalPersonList();
+ }
+
+ @Override
+ public Person getPersonInView() {
+ return personInView;
+ }
+
+ @Override
+ public void setPersonInView(Person person) {
+ this.personInView = person;
+ }
+
@Override
public boolean hasPerson(Person person) {
requireNonNull(person);
@@ -97,19 +116,24 @@ public boolean hasPerson(Person person) {
@Override
public void deletePerson(Person target) {
addressBook.removePerson(target);
+ if (target.isSamePerson(personInView)) {
+ setPersonInView(null);
+ }
}
@Override
public void addPerson(Person person) {
addressBook.addPerson(person);
updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ setPersonInView(person);
}
@Override
public void setPerson(Person target, Person editedPerson) {
requireAllNonNull(target, editedPerson);
-
addressBook.setPerson(target, editedPerson);
+ updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ setPersonInView(editedPerson);
}
//=========== Filtered Person List Accessors =============================================================
@@ -127,6 +151,7 @@ public ObservableList getFilteredPersonList() {
public void updateFilteredPersonList(Predicate predicate) {
requireNonNull(predicate);
filteredPersons.setPredicate(predicate);
+ setPersonInView(null);
}
@Override
@@ -137,12 +162,12 @@ public boolean equals(Object obj) {
}
// instanceof handles nulls
- if (!(obj instanceof ModelManager)) {
+ if (!(obj instanceof ModelPersonManager)) {
return false;
}
// state check
- ModelManager other = (ModelManager) obj;
+ ModelPersonManager other = (ModelPersonManager) obj;
return addressBook.equals(other.addressBook)
&& userPrefs.equals(other.userPrefs)
&& filteredPersons.equals(other.filteredPersons);
diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
deleted file mode 100644
index c9b5868427c..00000000000
--- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package seedu.address.model.person;
-
-import java.util.List;
-import java.util.function.Predicate;
-
-import seedu.address.commons.util.StringUtil;
-
-/**
- * Tests that a {@code Person}'s {@code Name} matches any of the keywords given.
- */
-public class NameContainsKeywordsPredicate implements Predicate {
- private final List keywords;
-
- public NameContainsKeywordsPredicate(List keywords) {
- this.keywords = keywords;
- }
-
- @Override
- public boolean test(Person person) {
- return keywords.stream()
- .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword));
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof NameContainsKeywordsPredicate // instanceof handles nulls
- && keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check
- }
-
-}
diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/person/ReadOnlyAddressBook.java
similarity index 78%
rename from src/main/java/seedu/address/model/ReadOnlyAddressBook.java
rename to src/main/java/seedu/address/model/person/ReadOnlyAddressBook.java
index 6ddc2cd9a29..b441d444f0e 100644
--- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
+++ b/src/main/java/seedu/address/model/person/ReadOnlyAddressBook.java
@@ -1,7 +1,7 @@
-package seedu.address.model;
+package seedu.address.model.person;
import javafx.collections.ObservableList;
-import seedu.address.model.person.Person;
+import seedu.address.model.person.person.Person;
/**
* Unmodifiable view of an address book
diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/person/Email.java
similarity index 87%
rename from src/main/java/seedu/address/model/person/Email.java
rename to src/main/java/seedu/address/model/person/person/Email.java
index a5bbe0b6a5f..c615163c687 100644
--- a/src/main/java/seedu/address/model/person/Email.java
+++ b/src/main/java/seedu/address/model/person/person/Email.java
@@ -1,4 +1,4 @@
-package seedu.address.model.person;
+package seedu.address.model.person.person;
import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.AppUtil.checkArgument;
@@ -10,15 +10,15 @@
public class Email {
private static final String SPECIAL_CHARACTERS = "!#$%&'*+/=?`{|}~^.-";
- public static final String MESSAGE_CONSTRAINTS = "Emails should be of the format local-part@domain "
+ public static final String MESSAGE_CONSTRAINTS = "Email should be in local-part@domain format "
+ "and adhere to the following constraints:\n"
+ "1. The local-part should only contain alphanumeric characters and these special characters, excluding "
- + "the parentheses, (" + SPECIAL_CHARACTERS + ") .\n"
- + "2. This is followed by a '@' and then a domain name. "
+ + "the parentheses, (" + SPECIAL_CHARACTERS + ").\n"
+ + "2. This is followed by an '@' and then a domain name. "
+ "The domain name must:\n"
+ " - be at least 2 characters long\n"
+ " - start and end with alphanumeric characters\n"
- + " - consist of alphanumeric characters, a period or a hyphen for the characters in between, if any.";
+ + " - consist of alphanumeric characters, a period or a hyphen for the characters in between, if any";
// alphanumeric and special characters
private static final String LOCAL_PART_REGEX = "^[\\w" + SPECIAL_CHARACTERS + "]+";
private static final String DOMAIN_FIRST_CHARACTER_REGEX = "[^\\W_]"; // alphanumeric characters except underscore
diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/person/Name.java
similarity index 77%
rename from src/main/java/seedu/address/model/person/Name.java
rename to src/main/java/seedu/address/model/person/person/Name.java
index 79244d71cf7..1c1380c1a65 100644
--- a/src/main/java/seedu/address/model/person/Name.java
+++ b/src/main/java/seedu/address/model/person/person/Name.java
@@ -1,4 +1,4 @@
-package seedu.address.model.person;
+package seedu.address.model.person.person;
import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.AppUtil.checkArgument;
@@ -7,16 +7,16 @@
* Represents a Person's name in the address book.
* Guarantees: immutable; is valid as declared in {@link #isValidName(String)}
*/
-public class Name {
+public class Name implements Comparable {
public static final String MESSAGE_CONSTRAINTS =
- "Names should only contain alphanumeric characters and spaces, and it should not be blank";
+ "Name should only take alphabetic characters and (optionally) spaces.";
/*
* 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 static final String VALIDATION_REGEX = "[a-zA-Z]([a-z-A-Z\\s])*";
public final String fullName;
@@ -56,4 +56,9 @@ public int hashCode() {
return fullName.hashCode();
}
+ @Override
+ public int compareTo(Name name) {
+ requireNonNull(name);
+ return fullName.compareTo(name.fullName);
+ }
}
diff --git a/src/main/java/seedu/address/model/person/person/NameDescriptionContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/person/NameDescriptionContainsKeywordsPredicate.java
new file mode 100644
index 00000000000..e51e6bc400b
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/person/NameDescriptionContainsKeywordsPredicate.java
@@ -0,0 +1,34 @@
+package seedu.address.model.person.person;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+import seedu.address.commons.util.StringUtil;
+
+/**
+ * Tests that a {@code Person}'s {@code Name} matches any of the keywords given.
+ */
+public class NameDescriptionContainsKeywordsPredicate implements Predicate {
+ private final List keywords;
+
+ public NameDescriptionContainsKeywordsPredicate(List keywords) {
+ this.keywords = keywords;
+ }
+
+ @Override
+ public boolean test(Person person) {
+ return keywords.stream()
+ .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(
+ person.getName().fullName.replaceAll("\\W", " "), keyword)
+ || StringUtil.containsWordIgnoreCase(
+ person.getDescription().value.orElse("").replaceAll("\\W", " "), keyword));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof NameDescriptionContainsKeywordsPredicate // instanceof handles nulls
+ && keywords.equals(((NameDescriptionContainsKeywordsPredicate) other).keywords)); // state check
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/person/Person.java
similarity index 60%
rename from src/main/java/seedu/address/model/person/Person.java
rename to src/main/java/seedu/address/model/person/person/Person.java
index 557a7a60cd5..bf0767ca42b 100644
--- a/src/main/java/seedu/address/model/person/Person.java
+++ b/src/main/java/seedu/address/model/person/person/Person.java
@@ -1,13 +1,10 @@
-package seedu.address.model.person;
+package seedu.address.model.person.person;
import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
-import java.util.Collections;
-import java.util.HashSet;
import java.util.Objects;
-import java.util.Set;
-import seedu.address.model.tag.Tag;
+import seedu.address.model.util.Description;
/**
* Represents a Person in the address book.
@@ -17,23 +14,23 @@ public class Person {
// Identity fields
private final Name name;
- private final Phone phone;
private final Email email;
// Data fields
- private final Address address;
- private final Set tags = new HashSet<>();
+ private final Role role;
+ private final Phone phone;
+ private final Description description;
/**
* 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, Role role, Description description) {
+ requireAllNonNull(name, phone, email, role, description);
this.name = name;
this.phone = phone;
this.email = email;
- this.address = address;
- this.tags.addAll(tags);
+ this.role = role;
+ this.description = description;
}
public Name getName() {
@@ -48,20 +45,16 @@ public Email getEmail() {
return email;
}
- public Address getAddress() {
- return address;
+ public Role getRole() {
+ return role;
}
- /**
- * Returns an immutable tag set, which throws {@code UnsupportedOperationException}
- * if modification is attempted.
- */
- public Set getTags() {
- return Collections.unmodifiableSet(tags);
+ public Description getDescription() {
+ return description;
}
/**
- * Returns true if both persons of the same name have at least one other identity field that is the same.
+ * Returns true if both persons of the same name and email.
* This defines a weaker notion of equality between two persons.
*/
public boolean isSamePerson(Person otherPerson) {
@@ -71,7 +64,7 @@ public boolean isSamePerson(Person otherPerson) {
return otherPerson != null
&& otherPerson.getName().equals(getName())
- && (otherPerson.getPhone().equals(getPhone()) || otherPerson.getEmail().equals(getEmail()));
+ && otherPerson.getEmail().equals(getEmail());
}
/**
@@ -92,28 +85,29 @@ public boolean equals(Object other) {
return otherPerson.getName().equals(getName())
&& otherPerson.getPhone().equals(getPhone())
&& otherPerson.getEmail().equals(getEmail())
- && otherPerson.getAddress().equals(getAddress())
- && otherPerson.getTags().equals(getTags());
+ && otherPerson.getDescription().equals(getDescription())
+ && otherPerson.getRole().equals(getRole());
}
@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, role, description);
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
- builder.append(getName())
- .append(" Phone: ")
- .append(getPhone())
+ builder.append("Name: ")
+ .append(getName())
+ .append(" Role: ")
+ .append(getRole())
.append(" Email: ")
.append(getEmail())
- .append(" Address: ")
- .append(getAddress())
- .append(" Tags: ");
- getTags().forEach(builder::append);
+ .append(" Phone: ")
+ .append(getPhone())
+ .append(" Description: ")
+ .append(getDescription());
return builder.toString();
}
diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/person/Phone.java
similarity index 54%
rename from src/main/java/seedu/address/model/person/Phone.java
rename to src/main/java/seedu/address/model/person/person/Phone.java
index 872c76b382f..c62535a0a4e 100644
--- a/src/main/java/seedu/address/model/person/Phone.java
+++ b/src/main/java/seedu/address/model/person/person/Phone.java
@@ -1,8 +1,10 @@
-package seedu.address.model.person;
+package seedu.address.model.person.person;
import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.AppUtil.checkArgument;
+import java.util.Optional;
+
/**
* Represents a Person's phone number in the address book.
* Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)}
@@ -11,9 +13,27 @@ public class Phone {
public static final String MESSAGE_CONSTRAINTS =
- "Phone numbers should only contain numbers, and it should be at least 3 digits long";
+ "Phone should only take numbers that are at least 3 digits long.";
public static final String VALIDATION_REGEX = "\\d{3,}";
- public final String value;
+
+ private static final String EMPTY_PHONE_FIELD = "-";
+ public final Optional value;
+
+ /**
+ * Constructs a {@code Phone}.
+ *
+ * @param phone A valid phone number.
+ */
+ public Phone(Optional phone) {
+ if (phone.isPresent()) {
+ checkArgument(isValidPhone(phone.get()), MESSAGE_CONSTRAINTS);
+ value = phone.get().isEmpty()
+ ? Optional.empty()
+ : phone;
+ } else {
+ value = phone;
+ }
+ }
/**
* Constructs a {@code Phone}.
@@ -23,19 +43,27 @@ public class Phone {
public Phone(String phone) {
requireNonNull(phone);
checkArgument(isValidPhone(phone), MESSAGE_CONSTRAINTS);
- value = phone;
+ if (phone.isEmpty()) {
+ value = Optional.empty();
+ } else {
+ value = Optional.of(phone);
+ }
}
/**
* Returns true if a given string is a valid phone number.
*/
public static boolean isValidPhone(String test) {
- return test.matches(VALIDATION_REGEX);
+ if (test.isEmpty()) {
+ return true;
+ } else {
+ return test.matches(VALIDATION_REGEX);
+ }
}
@Override
public String toString() {
- return value;
+ return value.orElse(EMPTY_PHONE_FIELD);
}
@Override
diff --git a/src/main/java/seedu/address/model/person/person/Role.java b/src/main/java/seedu/address/model/person/person/Role.java
new file mode 100644
index 00000000000..0145310c40d
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/person/Role.java
@@ -0,0 +1,65 @@
+package seedu.address.model.person.person;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+import static seedu.address.commons.util.StringUtil.getStringJoinedBySeparator;
+
+import java.util.Arrays;
+
+/**
+ * Represents a Person's role in the address book.
+ * Guarantees: immutable;
+ */
+public enum Role {
+
+ DEVELOPER("Developer", "dev"),
+ STAKEHOLDER("Stakeholder", "stk");
+
+ public static final String MESSAGE_CONSTRAINTS =
+ "Role should only take " + getStringJoinedBySeparator(Arrays.stream(Role.values())
+ .map(role -> role.getArgument() + " (" + role.toString().toLowerCase() + ")"), " or ") + ".";
+ public static final String VALIDATION_REGEX =
+ getStringJoinedBySeparator(Arrays.stream(Role.values()).map(role -> role.getArgument()), "|");
+
+ private final String name;
+ private final String argument;
+
+ private Role(String name, String argument) {
+ this.name = name;
+ this.argument = argument;
+ }
+
+ public static Role getRole(String arg) {
+ requireNonNull(arg);
+ checkArgument(isValidRole(arg), MESSAGE_CONSTRAINTS);
+ Role role = Role.getEnumByArgument(arg);
+ requireNonNull(role);
+ return role;
+ }
+
+ /**
+ * Returns true if a given string is a valid role.
+ */
+ public static boolean isValidRole(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ public static Role getEnumByArgument(String argument) {
+ for (Role role : Role.values()) {
+ if (role.argument.equals(argument)) {
+ return role;
+ }
+ }
+ return null;
+ }
+
+ public String getArgument() {
+ return argument;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/person/UniquePersonList.java
similarity index 85%
rename from src/main/java/seedu/address/model/person/UniquePersonList.java
rename to src/main/java/seedu/address/model/person/person/UniquePersonList.java
index 0fee4fe57e6..94c21bf30ea 100644
--- a/src/main/java/seedu/address/model/person/UniquePersonList.java
+++ b/src/main/java/seedu/address/model/person/person/UniquePersonList.java
@@ -1,15 +1,17 @@
-package seedu.address.model.person;
+package seedu.address.model.person.person;
import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
-import seedu.address.model.person.exceptions.DuplicatePersonException;
-import seedu.address.model.person.exceptions.PersonNotFoundException;
+import seedu.address.model.person.person.exceptions.DuplicatePersonException;
+import seedu.address.model.person.person.exceptions.PersonNotFoundException;
+import seedu.address.model.person.util.PersonComparator;
/**
* A list of persons that enforces uniqueness between its elements and does not allow nulls.
@@ -27,6 +29,7 @@ public class UniquePersonList implements Iterable {
private final ObservableList internalList = FXCollections.observableArrayList();
private final ObservableList internalUnmodifiableList =
FXCollections.unmodifiableObservableList(internalList);
+ private final PersonComparator personComparator = new PersonComparator();
/**
* Returns true if the list contains an equivalent person as the given argument.
@@ -46,6 +49,7 @@ public void add(Person toAdd) {
throw new DuplicatePersonException();
}
internalList.add(toAdd);
+ sortList();
}
/**
@@ -66,6 +70,7 @@ public void setPerson(Person target, Person editedPerson) {
}
internalList.set(index, editedPerson);
+ sortList();
}
/**
@@ -82,6 +87,7 @@ public void remove(Person toRemove) {
public void setPersons(UniquePersonList replacement) {
requireNonNull(replacement);
internalList.setAll(replacement.internalList);
+ sortList();
}
/**
@@ -95,6 +101,12 @@ public void setPersons(List persons) {
}
internalList.setAll(persons);
+ sortList();
+ }
+
+ /** Returns the internal list of contacts */
+ public ObservableList getInternalPersonList() {
+ return internalList;
}
/**
@@ -104,8 +116,16 @@ public ObservableList asUnmodifiableObservableList() {
return internalUnmodifiableList;
}
+ /**
+ * Sort the list alphabetically according to Name.
+ */
+ public void sortList() {
+ Collections.sort(internalList, personComparator);
+ }
+
@Override
public Iterator iterator() {
+ sortList();
return internalList.iterator();
}
diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/address/model/person/person/exceptions/DuplicatePersonException.java
similarity index 68%
rename from src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java
rename to src/main/java/seedu/address/model/person/person/exceptions/DuplicatePersonException.java
index d7290f59442..2df28d4f22c 100644
--- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java
+++ b/src/main/java/seedu/address/model/person/person/exceptions/DuplicatePersonException.java
@@ -1,4 +1,4 @@
-package seedu.address.model.person.exceptions;
+package seedu.address.model.person.person.exceptions;
/**
* Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same
@@ -6,6 +6,6 @@
*/
public class DuplicatePersonException extends RuntimeException {
public DuplicatePersonException() {
- super("Operation would result in duplicate persons");
+ super("Operation would result in duplicate contacts");
}
}
diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/person/exceptions/PersonNotFoundException.java
similarity index 72%
rename from src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java
rename to src/main/java/seedu/address/model/person/person/exceptions/PersonNotFoundException.java
index fa764426ca7..ae03ccb261c 100644
--- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java
+++ b/src/main/java/seedu/address/model/person/person/exceptions/PersonNotFoundException.java
@@ -1,4 +1,4 @@
-package seedu.address.model.person.exceptions;
+package seedu.address.model.person.person.exceptions;
/**
* Signals that the operation is unable to find the specified person.
diff --git a/src/main/java/seedu/address/model/person/util/PersonComparator.java b/src/main/java/seedu/address/model/person/util/PersonComparator.java
new file mode 100644
index 00000000000..898bf8a1d9f
--- /dev/null
+++ b/src/main/java/seedu/address/model/person/util/PersonComparator.java
@@ -0,0 +1,24 @@
+package seedu.address.model.person.util;
+
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.Comparator;
+
+import seedu.address.model.person.person.Name;
+import seedu.address.model.person.person.Person;
+
+/**
+ * Represents a Comparator for Person which sorts by Name.
+ */
+public class PersonComparator implements Comparator {
+ /**
+ * Compares Persons by Name in alphabetical order.
+ */
+ public int compare(Person a, Person b) {
+ requireAllNonNull(a, b);
+ Name aName = a.getName();
+ Name bName = b.getName();
+
+ return aName.compareTo(bName);
+ }
+}
diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java
deleted file mode 100644
index b0ea7e7dad7..00000000000
--- a/src/main/java/seedu/address/model/tag/Tag.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package seedu.address.model.tag;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.AppUtil.checkArgument;
-
-/**
- * Represents a Tag in the address book.
- * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)}
- */
-public class Tag {
-
- public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric";
- public static final String VALIDATION_REGEX = "\\p{Alnum}+";
-
- public final String tagName;
-
- /**
- * Constructs a {@code Tag}.
- *
- * @param tagName A valid tag name.
- */
- public Tag(String tagName) {
- requireNonNull(tagName);
- checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS);
- this.tagName = tagName;
- }
-
- /**
- * Returns true if a given string is a valid tag name.
- */
- public static boolean isValidTagName(String test) {
- return test.matches(VALIDATION_REGEX);
- }
-
- @Override
- public boolean equals(Object other) {
- return other == this // short circuit if same object
- || (other instanceof Tag // instanceof handles nulls
- && tagName.equals(((Tag) other).tagName)); // state check
- }
-
- @Override
- public int hashCode() {
- return tagName.hashCode();
- }
-
- /**
- * Format state as text for viewing.
- */
- public String toString() {
- return '[' + tagName + ']';
- }
-
-}
diff --git a/src/main/java/seedu/address/model/util/Contacts.java b/src/main/java/seedu/address/model/util/Contacts.java
new file mode 100644
index 00000000000..e6b67807683
--- /dev/null
+++ b/src/main/java/seedu/address/model/util/Contacts.java
@@ -0,0 +1,89 @@
+package seedu.address.model.util;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import java.util.Optional;
+
+/**
+ * Represents a Meeting's contacts in the meeting book.
+ */
+public class Contacts {
+ public static final String EMPTY_CONTACTS_FIELD = "-";
+ public static final String MESSAGE_CONSTRAINTS =
+ "Contacts should only take a name, a comma-separated string of names, or blank.";
+
+ /*
+ * Contacts can only take alphabetic characters and spaces separated with commas.
+ * First character should not be a blank string.
+ *
+ * @@author claraadora-reused
+ * Reused from https://stackoverflow.com/a/1396228 with minor modifications
+ */
+ public static final String VALIDATION_REGEX = "([a-zA-Z\\s]+)(,\\s*[a-zA-Z\\s]+)*";
+
+ /*
+ * Represents the value of Contacts.
+ */
+ public final Optional value;
+
+ /**
+ * Constructs a {@code Contacts}.
+ *
+ * @param contacts A valid Optional of contact name strings.
+ */
+ public Contacts(Optional contacts) {
+ if (contacts.isPresent()) {
+ checkArgument(isValidContacts(contacts.get()), MESSAGE_CONSTRAINTS);
+ value = contacts.get().isEmpty()
+ ? Optional.empty()
+ : contacts;
+ } else {
+ value = contacts;
+ }
+ }
+
+ /**
+ * Constructs a {@code Contacts}.
+ *
+ * @param contacts A valid Optional of contact name strings.
+ */
+ public Contacts(String contacts) {
+ requireNonNull(contacts);
+ checkArgument(isValidContacts(contacts), MESSAGE_CONSTRAINTS);
+ if (contacts.isEmpty()) {
+ value = Optional.empty();
+ } else {
+ value = Optional.of(contacts);
+ }
+ }
+
+ /**
+ * Returns true if a given string is a valid contacts.
+ */
+ public static boolean isValidContacts(String test) {
+ if (test.isEmpty()) {
+ return true;
+ } else {
+ return test.matches(VALIDATION_REGEX);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return value.orElse(EMPTY_CONTACTS_FIELD);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this
+ || (other instanceof Contacts
+ && value.equals(((Contacts) other).value));
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/util/DateTime.java b/src/main/java/seedu/address/model/util/DateTime.java
new file mode 100644
index 00000000000..b5dc81393dc
--- /dev/null
+++ b/src/main/java/seedu/address/model/util/DateTime.java
@@ -0,0 +1,115 @@
+package seedu.address.model.util;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+
+public class DateTime implements Comparable {
+ public static final String DATE_REGEX = "(([0-2]\\d)|(3[0-1]))-((0[1-9])|(1[0-2]))-(\\d{4})";
+ public static final String EARLIEST_DATE_STRING = "01-01-2019 00:00";
+ public static final String CONSTRAINTS =
+ "%s should be in dd-MM-yyyy HH:mm format and must not be earlier than the year 2019.";
+
+ public static final String MESSAGE_CONSTRAINTS = String.format(CONSTRAINTS, "Date");
+
+ public static final String VALIDATION_REGEX = String.format("%s(\\s(%s))",
+ DATE_REGEX, Time.TIME_REGEX);
+
+ public static final DateTimeFormatter DATE_TIME_FORMATTER =
+ DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm");
+
+ public final LocalDateTime value;
+
+ /**
+ * Constructs a {@code date}.
+ *
+ * @param date A valid date.
+ */
+ public DateTime(String date) {
+ requireNonNull(date);
+
+ // Check for constraints
+ checkArgument(isValidDateTime(date), MESSAGE_CONSTRAINTS);
+
+ //Parse value
+ this.value = LocalDateTime.parse(date, DATE_TIME_FORMATTER);
+ }
+
+ /**
+ * Returns the LocalDateTime value object.
+ *
+ * @return the value of DateTime.
+ */
+ public LocalDateTime getLocalDateTime() {
+ return this.value;
+ }
+
+ /**
+ * Returns true if a given string is a valid DateTime.
+ *
+ * @param test string to test.
+ * @return result of match.
+ */
+ public static boolean isValidDateTime(String test) {
+ return isValidDateTimePattern(test) && isValidDateTimeRange(test);
+ }
+
+
+ /**
+ * Returns true if a given string is a valid DateTime.
+ *
+ * @param test string to test.
+ * @return result of match.
+ */
+ public static boolean isValidDateTimePattern(String test) {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ /**
+ * Returns true if a given string is a valid DateTime.
+ *
+ * @param test string to test.
+ * @return result of match.
+ */
+ public static boolean isValidDateTimeRange(String test) {
+ try {
+ DateFormat df = new SimpleDateFormat("dd-MM-yyyy HH:mm");
+ df.setLenient(false);
+ Date date = df.parse(test);
+ Date earliestDate = df.parse(EARLIEST_DATE_STRING);
+ return !date.before(earliestDate);
+
+ } catch (ParseException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public int compareTo(DateTime o) {
+ return this.value.compareTo(o.value);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof DateTime) // instanceof handles nulls
+ && ((this.value.equals(((DateTime) other).value)));
+ }
+
+ @Override
+ public String toString() {
+ return this.value.format(DATE_TIME_FORMATTER);
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/util/Description.java b/src/main/java/seedu/address/model/util/Description.java
new file mode 100644
index 00000000000..8517565475e
--- /dev/null
+++ b/src/main/java/seedu/address/model/util/Description.java
@@ -0,0 +1,82 @@
+package seedu.address.model.util;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import java.util.Optional;
+
+/**
+ * Represents a Deliverable's description in the deliverable book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidDescription(String)}
+ */
+
+public class Description {
+
+ public static final String EMPTY_DESCRIPTION_FIELD = "-";
+ public static final String MESSAGE_CONSTRAINTS = "Descriptions can take any value.";
+ public static final String VALIDATION_REGEX = "[^\\s].*";
+ public final Optional value;
+
+ /**
+ * Constructs a {@code Description}.
+ *
+ * @param description A valid description.
+ */
+ public Description(Optional description) {
+ if (description.isPresent()) {
+ checkArgument(isValidDescription(description.get()), MESSAGE_CONSTRAINTS);
+ value = description.get().isEmpty()
+ ? Optional.empty()
+ : description;
+ } else {
+ value = description;
+ }
+
+ }
+
+ /**
+ * Constructs a {@code Description}.
+ *
+ * @param description A valid description.
+ */
+ public Description(String description) {
+ requireNonNull(description);
+ checkArgument(isValidDescription(description), MESSAGE_CONSTRAINTS);
+ if (description.isEmpty()) {
+ value = Optional.empty();
+ } else {
+ value = Optional.of(description);
+ }
+ }
+
+ /**
+ * Returns true if a given string is a valid description.
+ */
+ public static boolean isValidDescription(String test) {
+ if (test.isEmpty()) {
+ return true;
+ } else {
+ return test.matches(VALIDATION_REGEX);
+ }
+
+ }
+
+
+ @Override
+ public String toString() {
+ return value.orElse(EMPTY_DESCRIPTION_FIELD);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this // short circuit if same object
+ || (other instanceof Description) // instanceof handles nulls
+ && value.equals((((Description) other).value)); // state check
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java
index 1806da4facf..b4a84d52e82 100644
--- a/src/main/java/seedu/address/model/util/SampleDataUtil.java
+++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java
@@ -1,17 +1,25 @@
package seedu.address.model.util;
-import java.util.Arrays;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import seedu.address.model.AddressBook;
-import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.Phone;
-import seedu.address.model.tag.Tag;
+import java.util.Optional;
+
+import seedu.address.model.deliverable.DeliverableBook;
+import seedu.address.model.deliverable.ReadOnlyDeliverableBook;
+import seedu.address.model.deliverable.deliverable.Deadline;
+import seedu.address.model.deliverable.deliverable.Deliverable;
+import seedu.address.model.deliverable.deliverable.Milestone;
+import seedu.address.model.meeting.MeetingBook;
+import seedu.address.model.meeting.ReadOnlyMeetingBook;
+import seedu.address.model.meeting.meeting.From;
+import seedu.address.model.meeting.meeting.Location;
+import seedu.address.model.meeting.meeting.Meeting;
+import seedu.address.model.meeting.meeting.To;
+import seedu.address.model.person.AddressBook;
+import seedu.address.model.person.ReadOnlyAddressBook;
+import seedu.address.model.person.person.Email;
+import seedu.address.model.person.person.Name;
+import seedu.address.model.person.person.Person;
+import seedu.address.model.person.person.Phone;
+import seedu.address.model.person.person.Role;
/**
* Contains utility methods for populating {@code AddressBook} with sample data.
@@ -20,23 +28,198 @@ public class SampleDataUtil {
public static Person[] getSamplePersons() {
return new Person[] {
new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"),
- new Address("Blk 30 Geylang Street 29, #06-40"),
- getTagSet("friends")),
+ Role.getRole("stk"), new Description("End user")),
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")),
+ Role.getRole("dev"), new Description("Frontend Engineer")),
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 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 Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"),
- new Address("Blk 47 Tampines Street 20, #17-35"),
- getTagSet("classmates")),
+ Role.getRole("stk"), new Description("Business analyst")),
+ new Person(new Name("David Li"), new Phone(Optional.empty()), new Email("lidavid@example.com"),
+ Role.getRole("dev"), new Description("Backend Engineer")),
+ new Person(new Name("Irfan Ibrahim"), new Phone(Optional.empty()), new Email("irfan@example.com"),
+ Role.getRole("stk"), new Description(Optional.empty())),
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"))
+ Role.getRole("stk"), new Description("End user")),
+ new Person(new Name("Bianca Li"), new Phone("1234567"), new Email("bianca@example.com"),
+ Role.getRole("stk"), new Description("Product Designer")),
+ new Person(new Name("Budi Putra"), new Phone("1234566"), new Email("budi@example.com"),
+ Role.getRole("dev"), new Description("DevOps Engineer")),
+ new Person(new Name("Amber Johnson"), new Phone("81623941"), new Email("amber@example.com"),
+ Role.getRole("stk"), new Description("Finance expert")),
+ new Person(new Name("Samuel Sam"), new Phone("78129394"), new Email("samsam@example.com"),
+ Role.getRole("dev"), new Description("Full-stack Engineer")),
+ new Person(new Name("Charlie Oliver"), new Phone("93210283"), new Email("charlie@example.com"),
+ Role.getRole("stk"), new Description("Business analyst. Alex works with him.")),
+ new Person(new Name("Devina Yu"), new Phone(Optional.empty()), new Email("devina@example.com"),
+ Role.getRole("dev"), new Description("Full-stack Engineer")),
+ new Person(new Name("Carol Geller"), new Phone(Optional.empty()), new Email("carol@example.com"),
+ Role.getRole("stk"), new Description("Potential Customer")),
+ new Person(new Name("Martin Seth"), new Phone(Optional.empty()), new Email("martin@example.com"),
+ Role.getRole("stk"), new Description("Potential Customer")),
+ new Person(new Name("Rosa Sinantra"), new Phone("92624417"), new Email("rosa@example.com"),
+ Role.getRole("stk"), new Description("End user")),
+ new Person(new Name("Bonny Thompson"), new Phone("1234444"), new Email("thompson@example.com"),
+ Role.getRole("stk"), new Description("Works at Johnson & Johnson")),
+ new Person(new Name("Lindsay Perry"), new Phone("12343337"), new Email("lindsay@example.com"),
+ Role.getRole("dev"), new Description("Senior UI/UX Designer")),
+ new Person(new Name("Robbie Margaret"), new Phone("92624417"), new Email("robbie@example.com"),
+ Role.getRole("stk"), new Description("End user")),
+ new Person(new Name("Zachary Quinn"), new Phone("123123123"), new Email("zachary@example.com"),
+ Role.getRole("stk"), new Description("Business Associates")),
+ new Person(new Name("Christian Pine"), new Phone("321321321"), new Email("pine@example.com"),
+ Role.getRole("dev"), new Description("QA engineer")),
+ new Person(new Name("Cassandra Bullock"), new Phone("56565656"), new Email("cassandra@example.com"),
+ Role.getRole("dev"), new Description("QA engineer")),
+ new Person(new Name("Robby Williams"), new Phone("12346123"), new Email("robby@example.com"),
+ Role.getRole("stk"), new Description("Head of Engineering")),
+ new Person(new Name("Justin Gomez"), new Phone("1234444"), new Email("justin@example.com"),
+ Role.getRole("stk"), new Description("Business Analyst")),
+ new Person(new Name("Sarah McQuarie"), new Phone("12343337"), new Email("mcquarie@example.com"),
+ Role.getRole("dev"), new Description("Data Scientist")),
+ new Person(new Name("Joe Manganiel"), new Phone("92624417"), new Email("joe@example.com"),
+ Role.getRole("stk"), new Description("Head of business")),
+ new Person(new Name("Bryan Randall"), new Phone("123123123"), new Email("bryan@example.com"),
+ Role.getRole("stk"), new Description("Angel investor from Sequoia VC")),
+ new Person(new Name("Samuel Smith"), new Phone("321321321"), new Email("smith@example.com"),
+ Role.getRole("stk"), new Description("Investor from 600 startups")),
+ new Person(new Name("Farrah Lionel"), new Phone("56565656"), new Email("farrah@example.com"),
+ Role.getRole("stk"), new Description("Investor from Bank of the People")),
+ };
+ }
+
+ public static Deliverable[] getSampleDeliverables() {
+ return new Deliverable[] {
+ new Deliverable(new Title("Define problem to solve"), new Milestone("1.1"),
+ new Description("Survey potential customers to gather feedback."),
+ new Deadline("04-08-2020 15:00"), true, new Contacts("Carol Geller, Martin Seth")),
+ new Deliverable(new Title("Determine the Minimum Viable Product (MVP)"), new Milestone("1.2"),
+ new Description("Come up with minimum set of features to test key assumptions. "
+ + "Discuss with Bianca (Product Designer) too."),
+ new Deadline("18-08-2020 12:00"), true, new Contacts("Bianca Li")),
+ new Deliverable(new Title("Finish mock-ups"), new Milestone("1.3"),
+ new Description("Liaise with the UI/UX team. "
+ + "Lindsay can represent her team as senior UI/UX designer."),
+ new Deadline("01-09-2020 10:00"), true, new Contacts("Lindsay Lauren")),
+ new Deliverable(new Title("Finalise design and plan"), new Milestone("1.4"),
+ new Description("This is urgent and important!"),
+ new Deadline("15-09-2020 15:00"), true, new Contacts("Robby Williams, Breonna Randall")),
+ new Deliverable(new Title("Settle prioritization of features"), new Milestone("2.1"),
+ new Description("Hold meeting with the dev team."),
+ new Deadline("29-09-2020 12:00"), true, new Contacts("Bernice Yu, David Li, Budi Putra,"
+ + "Christian Pine, Cassandra Bullock, Samuel Sam, Devina Yu")),
+ new Deliverable(new Title("Deliver first version of MVP"), new Milestone("2.3"),
+ new Description("Check-in with Bernice (Tech Lead)."),
+ new Deadline("15-10-2020 10:00"), true, new Contacts("Bernice Yu")),
+ new Deliverable(new Title("Conduct MVP usability testing"), new Milestone("2.4"),
+ new Description("Schedule meetings with end users."),
+ new Deadline("20-10-2020 15:00"), true, new Contacts("Alex Yeoh, Roy Balakrishnan, "
+ + "Rosa Sinantra")),
+ new Deliverable(new Title("Refine design"), new Milestone("2.5"),
+ new Description("Discuss design with Product Designer and UI/UX team again."),
+ new Deadline("28-10-2020 12:00"), true, new Contacts("Lindsay Lauren, Bianca Li")),
+ new Deliverable(new Title("Deliver second version of MVP"), new Milestone("2.6"),
+ new Description("Reminder to plan time wisely."),
+ new Deadline("30-10-2020 10:00"), false, new Contacts("Bernice Yeoh")),
+ new Deliverable(new Title("Deliver find feature"), new Milestone("3.1"),
+ new Description("Check-in with Bernice (Tech Lead)."),
+ new Deadline("08-11-2020 15:00"), false, new Contacts("Bernice Yeoh")),
+ new Deliverable(new Title("Deliver sort feature"), new Milestone("3.1"),
+ new Description("Check-in with Bernice (Tech Lead)."),
+ new Deadline("08-11-2020 15:00"), false, new Contacts("Bernice Yeoh")),
+ new Deliverable(new Title("Define marketing goals "), new Milestone("3.1"),
+ new Description("Get financial consultation."),
+ new Deadline("10-11-2020 10:00"), false, new Contacts("Amber Johnson")),
+ new Deliverable(new Title("Finalise marketing goals"), new Milestone("3.2"),
+ new Description("Get financial consultation."),
+ new Deadline("12-11-2020 10:00"), false, new Contacts("Amber Johnson")),
+ new Deliverable(new Title("Deliver view feature"), new Milestone("3.2"),
+ new Description("Check-in with Bernice (Tech Lead)."),
+ new Deadline("20-11-2020 10:00"), false, new Contacts("Bernice Yu")),
+ new Deliverable(new Title("Product Launch \uD83D\uDE80"), new Milestone("10"),
+ new Description("Launch product to market"),
+ new Deadline("04-05-2021 23:59"), false, new Contacts(Optional.empty())),
+ };
+ }
+
+ public static Meeting[] getSampleMeetings() {
+ return new Meeting[] {
+ new Meeting(new Title("Survey potential customers"),
+ new Description("Gather feedback"),
+ new From("04-08-2020 15:00"),
+ new To("18:00"),
+ new Contacts("Carol Geller, Martin Seth"),
+ new Location("Stardollar Cafe")),
+ new Meeting(new Title("Discuss product design"), new Description("With product designer"),
+ new From("18-08-2020 12:00"),
+ new To("14:00"),
+ new Contacts("Bianca Li"),
+ new Location("Min Cafe")),
+ new Meeting(new Title("Discuss product mock-ups"), new Description("With UI/UX team. "
+ + "Can be represented by the senior UI/UX designer."),
+ new From("01-09-2020 10:00"),
+ new To("11:00"),
+ new Contacts("Lindsay Lauren"),
+ new Location("Meeting room C")),
+ new Meeting(new Title("Present plan to Heads"),
+ new Description("Don't forget to present survey results."),
+ new From("15-09-2020 15:00"),
+ new To("17:00"),
+ new Contacts("Robby Williams, Breonna Randall"),
+ new Location("Meeting room A")),
+ new Meeting(new Title("Discuss development plan"), new Description("With dev team"),
+ new From("29-09-2020 12:00"),
+ new To("14:00"),
+ new Contacts("Bernice Yu, David Li, Budi Putra,"
+ + "Christian Pine, Cassandra Bullock, Samuel Sam, Devina Yu"),
+ new Location("Meeting room B")),
+ new Meeting(new Title("Check-in about MVP progress"), new Description("With tech lead"),
+ new From("15-10-2020 12:00"),
+ new To("14:00"),
+ new Contacts("Bernice Yu"),
+ new Location("Zoom call")),
+ new Meeting(new Title("Check-in about MVP completion"), new Description("Goals must be achieved!"),
+ new From("17-10-2020 10:00"),
+ new To("14:00"),
+ new Contacts("Bernice Yu"),
+ new Location("Zoom call")),
+ new Meeting(new Title("Meeting end users for usability testing"), new Description("With end users"),
+ new From("20-10-2020 10:00"),
+ new To("16:00"),
+ new Contacts("Alex Yeoh, Roy Balakrishnan, Rosa Sinantra"),
+ new Location("Stardollar Cafe")),
+ new Meeting(new Title("Discuss refinement of design and plan"),
+ new Description("With Product Designer and UI/UX team"),
+ new From("28-10-2020 10:00"),
+ new To("16:00"),
+ new Contacts("Lindsay Lauren, Bianca Li"),
+ new Location("Zoom call")),
+ new Meeting(new Title("Consult about marketing goals"),
+ new Description("With finance expert"),
+ new From("09-11-2020 10:00"),
+ new To("16:00"),
+ new Contacts("Amber Johnson"),
+ new Location("Zoom call")),
+ new Meeting(new Title("Scrum meeting"), new Description("With dev team"),
+ new From("10-11-2020 10:00"),
+ new To("14:00"),
+ new Contacts("Bernice Yu, David Li, Budi Putra,"
+ + "Christian Pine, Cassandra Bullock, Samuel Sam, Devina Yu"),
+ new Location("Zoom call")),
+ new Meeting(new Title("Finalise marketing plan"),
+ new Description("With finance expert"),
+ new From("11-11-2020 10:00"),
+ new To("16:00"),
+ new Contacts("Amber Johnson"),
+ new Location("Zoom call")),
+ new Meeting(new Title("Discuss current progress"), new Description("With Head of Business"),
+ new From("20-11-2020 10:00"),
+ new To("14:00"),
+ new Contacts("Joe Manganiel"),
+ new Location("Zoom call")),
+ new Meeting(new Title("Provide updates on MVP"), new Description("With Head of Engineering"),
+ new From("21-11-2020 10:00"),
+ new To("14:00"),
+ new Contacts("Robby Williams"),
+ new Location("Zoom call")),
};
}
@@ -48,13 +231,20 @@ public static ReadOnlyAddressBook getSampleAddressBook() {
return sampleAb;
}
- /**
- * Returns a tag set containing the list of strings given.
- */
- public static Set getTagSet(String... strings) {
- return Arrays.stream(strings)
- .map(Tag::new)
- .collect(Collectors.toSet());
+ public static ReadOnlyDeliverableBook getSampleDeliverableBook() {
+ DeliverableBook sampleDb = new DeliverableBook();
+ for (Deliverable sampleDeliverable : getSampleDeliverables()) {
+ sampleDb.addDeliverable(sampleDeliverable);
+ }
+ return sampleDb;
+ }
+
+ public static ReadOnlyMeetingBook getSampleMeetingBook() {
+ MeetingBook sampleMb = new MeetingBook();
+ for (Meeting sampleMeeting : getSampleMeetings()) {
+ sampleMb.addMeeting(sampleMeeting);
+ }
+ return sampleMb;
}
}
diff --git a/src/main/java/seedu/address/model/util/Time.java b/src/main/java/seedu/address/model/util/Time.java
new file mode 100644
index 00000000000..54bfba56e51
--- /dev/null
+++ b/src/main/java/seedu/address/model/util/Time.java
@@ -0,0 +1,86 @@
+package seedu.address.model.util;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+
+public class Time implements Comparable