diff --git a/README.md b/README.md index 16208adb9b6..2c52097a2c9 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,13 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) - -![Ui](docs/images/Ui.png) - -* This is **a sample project for Software Engineering (SE) students**.
- Example usages: - * as a starting point of a course project (as opposed to writing everything from scratch) - * as a case study -* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details. - * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big. - * It comes with a **reasonable level of user and developer documentation**. -* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...). -* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**. -* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org/#contributing-to-se-edu) for more info. +[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/AY2425S1-CS2103T-F10-1/tp/actions)![Ui](docs/images/Ui.png) + +# This is **ClientHub**
+ Our product provides independent financial advisors with a streamlined tool to manage client details + (eg. Track insurance policies). Optimized for simplicity and efficiency, + this product makes the lives of financial advisors easier by offering easier access to + relevant information for their clients.
+ + +* For the detailed documentation of this project, see the **[ClientHub Product Website](https://ay2425s1-cs2103t-f10-1.github.io/tp/)**. +
+ +This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org). diff --git a/build.gradle b/build.gradle index 0db3743584e..f49650041bb 100644 --- a/build.gradle +++ b/build.gradle @@ -25,6 +25,10 @@ test { finalizedBy jacocoTestReport } +run { + enableAssertions = true +} + task coverage(type: JacocoReport) { sourceDirectories.from files(sourceSets.main.allSource.srcDirs) classDirectories.from files(sourceSets.main.output) @@ -66,7 +70,7 @@ dependencies { } shadowJar { - archiveFileName = 'addressbook.jar' + archiveFileName = 'clienthub.jar' } defaultTasks 'clean', 'test' diff --git a/docs/AboutUs.md b/docs/AboutUs.md index ff3f04abd02..b4029aba2f3 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -5,55 +5,55 @@ title: About Us We are a team based in the [School of Computing, National University of Singapore](https://www.comp.nus.edu.sg). -You can reach us at the email `seer[at]comp.nus.edu.sg` +You can reach us at the email `e1155908@u.nus.edu.sg` ## Project team -### John Doe +### Ahmad Syu'aib - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] +[[github](https://github.com/ahmadsyuaib)] [[portfolio](team/johndoe.md)] -* Role: Project Advisor +* Role: Developer +* Responsibilities: Developing -### Jane Doe +### Harith Nurhisham - + -[[github](http://github.com/johndoe)] +[[github](https://github.com/Harithhh06)] [[portfolio](team/johndoe.md)] -* Role: Team Lead -* Responsibilities: UI +* Role: Developer +* Responsibilities: Developing -### Johnny Doe +### Jeremy - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](http://github.com/jereeemyyyy)] [[portfolio](team/johndoe.md)] * Role: Developer -* Responsibilities: Data +* Responsibilities: UI -### Jean Doe +### Liu Rui - + -[[github](http://github.com/johndoe)] +[[github](http://github.com/L-rrrr)] [[portfolio](team/johndoe.md)] * Role: Developer -* Responsibilities: Dev Ops + Threading +* Responsibilities: No work required -### James Doe +### Rubin - + -[[github](http://github.com/johndoe)] +[[github](http://github.com/rubinnn)] [[portfolio](team/johndoe.md)] * Role: Developer -* Responsibilities: UI +* Responsibilities: Developing diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 743c65a49d2..cdd0a4c0ad6 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -51,7 +51,7 @@ The bulk of the app's work is done by the following four components: **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 Alice`. @@ -68,39 +68,39 @@ The sections below give more details of each component. ### UI component -The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java) +The **API** of this component is specified in [`Ui.java`](https://github.com/AY2425S1-CS2103T-F10-1/tp/blob/master/src/main/java/seedu/address/ui/Ui.java) ![Structure of the UI Component](images/UiClassDiagram.png) The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI. -The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml) +The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/AY2425S1-CS2103T-F10-1/tp/blob/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2425S1-CS2103T-F10-1/tp/blob/master/src/main/resources/view/MainWindow.fxml) The `UI` component, * executes user commands using the `Logic` component. * listens for changes to `Model` data so that the UI can be updated with the modified data. * keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands. -* depends on some classes in the `Model` component, as it displays `Person` object residing in the `Model`. +* depends on some classes in the `Model` component, as it displays `Person` and `Reminder` object residing in the `Model`. (For eg. `PersonListPanel` , `PersonCard`, `ReminderListPanel`, `ReminderCard`) ### Logic component -**API** : [`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java) +**API** : [`Logic.java`](https://github.com/AY2425S1-CS2103T-F10-1/tp/blob/master/src/main/java/seedu/address/logic/Logic.java) Here's a (partial) class diagram of the `Logic` component: -The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete 1")` API call as an example. +The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete Alice")` API call as an example. -![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png) +![Interactions Inside the Logic Component for the `delete Alice` Command](images/DeleteSequenceDiagram.png)
:information_source: **Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of diagram.
How the `Logic` component works: -1. When `Logic` is called upon to execute a command, it is passed to an `AddressBookParser` object which in turn creates a parser that matches the command (e.g., `DeleteCommandParser`) and uses it to parse the command. +1. When `Logic` is called upon to execute a command, it is passed to an `ClientHubParser` object which in turn creates a parser that matches the command (e.g., `DeleteCommandParser`) and uses it to parse the command. 1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `DeleteCommand`) which is executed by the `LogicManager`. 1. The command can communicate with the `Model` when it is executed (e.g. to delete a person).
Note that although this is shown as a single step in the diagram above (for simplicity), in the code it can take several interactions (between the command object and the `Model`) to achieve. @@ -111,23 +111,23 @@ Here are the other classes in `Logic` (omitted from the class diagram above) tha How the parsing works: -* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object. +* When called upon to parse a user command, the `ClientHubParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object. * All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing. ### Model component -**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java) +**API** : [`Model.java`](https://github.com/AY2425S1-CS2103T-F10-1/tp/blob/master/src/main/java/seedu/address/model/Model.java) The `Model` component, -* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object). -* stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* stores the client hub data i.e., all `Person` objects (which are contained in a `UniquePersonList` object) and all Reminders(which are contained in a ReminderList object). +* stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _sorted_ list composed using a _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. * stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects. * does not depend on any of the other three components (as the `Model` represents data entities of the domain, they should make sense on their own without depending on other components) -
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Tag` object per unique tag, instead of each `Person` needing their own `Tag` objects.
+
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Client Type` list in the `ClientHub`, which `Person` references. This allows `ClientHub` to only require one `ClientType` object per unique client type, instead of each `Person` needing their own `ClientType` objects.
@@ -141,8 +141,8 @@ The `Model` component, The `Storage` component, -* can save both address book data and user preference data in JSON format, and read them back into corresponding objects. -* inherits from both `AddressBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed). +* can save both ClientHub data and user preference data in JSON format, and read them back into corresponding objects. +* inherits from both `ClientHubStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed). * depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`) ### Common classes @@ -155,179 +155,342 @@ Classes used by multiple components are in the `seedu.address.commons` package. This section describes some noteworthy details on how certain features are implemented. -### \[Proposed\] Undo/redo feature +### Add Reminder Feature -#### Proposed Implementation +#### Implementation +The add reminder mechanism is done by altering the `ReminderList` which is +facilitated by `ClientHub` class. Additionally , `ClientHub` also implements +the following operations: +- `ClientHub#editReminder()` - Edits the reminder in the reminder list. +- `ClientHub#deleteReminder()` - Deletes the reminder from the reminder list. -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: +Given below is an example usage scenario and how the add reminder mechanism behaves at each step. -* `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. +Step 1. The user launches the application for the first time. The `ClientHub` will be initialized with an empty reminder list and a initial. +Client list. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +Step 2. The user finds a client in the client list to add a reminder for for eg. `Alice Pauline`. The +User will then execute `radd n/Alice Pauline dt/2022-10-10 12:00 d/lunch` command to add a reminder for Alice Pauline. +The `radd` command calls `ClientHub#addReminder()`, causing the reminder to be added to the reminder list. -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. -Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. +The following sequence diagram shows how an add reminder operation goes through the `Logic` component: +![AddReminderSequenceDiagram-Logic.png](images/AddReminderSequenceDiagram-Logic.png) -![UndoRedoState0](images/UndoRedoState0.png) +Similarly, how an add reminder operation goes through the `Model` component is shown below: +![AddReminderSequenceDiagram-Model.png](images/AddReminderSequenceDiagram-Model.png) -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. +The following activity diagram summarizes what happens when a user executes a add reminder command: +![AddReminderCommandActivity.png](images/AddReminderCommandActivity.png) -![UndoRedoState1](images/UndoRedoState1.png) -Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. -![UndoRedoState2](images/UndoRedoState2.png) +-------------------------------------------------------------------------------------------------------------------- -
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`. +## **Documentation, logging, testing, configuration, dev-ops** -
+* [Documentation guide](Documentation.md) +* [Testing guide](Testing.md) +* [Logging guide](Logging.md) +* [Configuration guide](Configuration.md) +* [DevOps guide](DevOps.md) -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. +-------------------------------------------------------------------------------------------------------------------- -![UndoRedoState3](images/UndoRedoState3.png) +## **Appendix: Requirements** -
: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. +### Product scope -
+**Target user profile**: -The following sequence diagram shows how an undo operation goes through the `Logic` component: +* Tech-savvy Independent Financial Advisors who manage more than 50 clients. +* Have a need to efficiently manage a large volume of client details, such as insurance policies and financial plans. +* Prefer desktop apps over other types of interfaces for their daily work. +* Can type quickly and are comfortable using CLI applications, favoring typing over mouse-based interactions for efficiency. +* Require a simple and streamlined tool that makes it easy to access and track client information with minimal clicks. +* Value simplicity and efficiency in their tools to save time and focus on client relationships. +* Need a system that provides quick access to relevant client information, including financial plans, policy expiration dates, and client details. +* Often handle tasks that involve tracking insurance policies, renewals, and financial documents. -![UndoSequenceDiagram](images/UndoSequenceDiagram-Logic.png) +**Value proposition**: Our product provides independent financial advisors with a streamlined tool to manage client details (eg. Track insurance policies) as well as create reminders for meet ups with them. Optimized for simplicity and efficiency, this product makes the lives of financial advisors easier by offering easier access to relevant information of their clients and important dates their clients. -
: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. -
+### User stories -Similarly, how an undo operation goes through the `Model` component is shown below: +Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` -![UndoSequenceDiagram](images/UndoSequenceDiagram-Model.png) +| Priority | As a …​ | I want to …​ | So that I can…​ | +|----------|---------------------|---------------------------------------------------------------------------------|-----------------------------------------------| +| `* * *` | financial advisor | add a new client’s client details | easily manage my client base | +| `* * *` | financial advisor | view all of a client’s details on one screen | have all necessary information in one place. | +| `* * *` | financial advisor | delete outdated client client information | keep my database clean and relevant | +| `* *` | financial advisor | search for a client by their personal information, such as name or phone number | ensure that I always have the latest details | +| `* *` | financial advisor | sort all clients in alphabetical order based on their names | prioritize my communication with them | +| `*` | financial advisor | update a client’s client information | ensure that I always have the latest details | +| `*` | financial advisor | tag clients with specific client types (e.g., investor, retiree) | segment them for different services | +| `*` | financial advisor | add description to a client’s profile | track important interactions or discussions | +| `*` | financial advisor | assign reminders to each client (e.g., "Review investment portfolio") | stay organized and focused | -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. +*{More to be added}* -
: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. +### Use cases -
+(For all use cases below, the **System** is the `ClientHub` and the **Actor** is the `user`, unless specified otherwise) -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. +**Use case: Add a new client** -![UndoRedoState4](images/UndoRedoState4.png) +**MSS** +1. User inputs the command to add a client's client +2. ClientHub adds the new client to the list of clients +3. ClientHub shows successful output message -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. + Use case ends. -![UndoRedoState5](images/UndoRedoState5.png) +**Extensions** -The following activity diagram summarizes what happens when a user executes a new command: +* 1a. ClientHub detects invalid input format + * 1a1. ClientHub shows an error message and shows the correct format for the wrong input + Step 1 is repeated until user inputs the correct format. - -#### Design considerations: +* 1b. ClientHub detects missing fields -**Aspect: How undo & redo executes:** + * 1b1. ClientHub shows an error message informing user that required field(s) are missing -* **Alternative 1 (current choice):** Saves the entire address book. - * Pros: Easy to implement. - * Cons: May have performance issues in terms of memory usage. + Step 1 is repeated until user inputs all required fields. + -* **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. +* 1c. ClientHub detects a duplicate client -_{more aspects and alternatives to be added}_ + * 1c1. ClientHub informs user that there is more than one client with the same name and prompt the user to input a + different name. + + Step 1 is repeated until user inputs a unique name. + -### \[Proposed\] Data archiving +**Use case: Delete a client** -_{Explain here how the data archiving feature will be implemented}_ +**MSS** +1. User keys in the command to delete a client +2. ClientHub deletes the person +3. ClientHub shows successful output message --------------------------------------------------------------------------------------------------------------------- + Use case ends. -## **Documentation, logging, testing, configuration, dev-ops** +**Extensions** -* [Documentation guide](Documentation.md) -* [Testing guide](Testing.md) -* [Logging guide](Logging.md) -* [Configuration guide](Configuration.md) -* [DevOps guide](DevOps.md) +* 1a. ClientHub detects invalid input format --------------------------------------------------------------------------------------------------------------------- + * 1a1. ClientHub shows an error message that the input is in the wrong format -## **Appendix: Requirements** + Step 1 is repeated until user inputs the correct format. -### Product scope +* 1b. ClientHub detects that the given name is not in the list + + * 1b1. ClientHub shows an error message that the name is not in the list -**Target user profile**: + Use case ends. -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing to mouse interactions -* is reasonably comfortable using CLI apps +* 1c. ClientHub detects multiple clients of the same name -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app + * 1c1. ClientHub informs the user that there are multiple clients with the same name and requests the user to input the full name of the client + Step 1 is repeated until user inputs full name of the client. -### User stories +**Use case: List clients** -Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` +**MSS** +1. User requests to list all clients +2. ClientHub shows the list of all clients saved -| 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 | +**Extensions** -*{More to be added}* +* 1a. ClientHub detects unknown command + * 1a1. ClientHub shows an error message that the command is not recognized -### Use cases + Use case ends. -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) +**Use case: Search for a client** -**Use case: Delete a person** +**MSS** +1. User inputs the command to search for a client +2. ClientHub searches for the client +3. ClientHub shows the client details + + Use case ends. + +**Extensions** + +* 1a. ClientHub detects invalid input format + * 1a1. ClientHub shows an error message that the input is in the wrong format + + Step 1 is repeated until user inputs the correct format. +* 1b. ClientHub detects that the given name is not in the list + * 1b1. ClientHub shows an error message that the name is not in the list + + Use case ends. +* 1c. ClientHub detects multiple clients that matches the same name + * 1c1. ClientHub informs the user that multiple clients matches the same name and requests the user to input the full name of the client + + Step 1 is repeated until user inputs full name of the client. + +**Use case: Edit a client** **MSS** +1. User inputs the command to edit a client's contact details +2. ClientHub edits the client details +3. ClientHub shows successful output message + + Use case ends. + +**Extensions** -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 +* 1a. ClientHub detects invalid input format + * 1a1. ClientHub shows an error message that the input is in the wrong format + + Step 1 is repeated until user inputs the correct format. +* 1b. ClientHub detects that the given name is not in the list + * 1b1. ClientHub shows an error message that the name is not in the list + + Use case ends. +* 1c. ClientHub detects multiple clients of the same name + * 1c1. ClientHub informs the user that there are multiple clients with the same name and requests the user to input the full name of the client + + Step 1 is repeated until user inputs full name of the client. + +**Use case: Sort clients** + +**MSS** + +1. User inputs the command to sort clients +2. ClientHub sorts the clients in alphabetical order based on their names +3. ClientHub shows the sorted list of clients Use case ends. **Extensions** -* 2a. The list is empty. +* 1a. ClientHub detects unknown command + * 1a1. ClientHub shows an error message that the command is not recognized - Use case ends. + Use case ends. +* 1b. ClientHub detects invalid input format + * 1b1. ClientHub shows an error message that the input is in the wrong format -* 3a. The given index is invalid. + Step 1 is repeated until user inputs the correct format. - * 3a1. AddressBook shows an error message. - Use case resumes at step 2. +**Use case: View** -*{More to be added}* +**MSS** + +1. User inputs the command to view a client's details +2. ClientHub shows the client's details + + Use case ends. + +**Extensions** +1. ClientHub detects invalid input format + * 1a1. ClientHub shows an error message that the input is in the wrong format + + Step 1 is repeated until user inputs the correct format. +* 1b. ClientHub detects that the given name is not in the list + * 1b1. ClientHub shows an error message that the name is not in the list + + Use case ends. +* 1c. ClientHub detects multiple clients of the same name + * 1c1. ClientHub informs the user that there are multiple clients with the same name and requests the user to input the full name of the client + + Step 1 is repeated until user inputs full name of the client. + +**Use case: Add reminder** + +**MSS** + +1. User inputs the command to add a reminder +2. ClientHub adds the reminder to the client +3. ClientHub shows successful output message + Use case ends. + +**Extensions** + +* 1a. ClientHub detects invalid input format + * 1a1. ClientHub shows an error message that the input is in the wrong format + + Step 1 is repeated until user inputs the correct format. +* 1b. ClientHub detects that the given name is not in the list + * 1b1. ClientHub shows an error message that the name is not in the list + + Use case ends. +* 1c. ClientHub detects multiple clients of the same name + * 1c1. ClientHub informs the user that there are multiple clients with the same name and requests the user to input the full name of the client + + Step 1 is repeated until user inputs full name of the client. +* 1d. ClientHub detects that the reminder is already added + * 1d1. ClientHub shows an error message that the reminder is already added + + Use case ends. + +**Use case: Delete reminder** + +**MSS** + +1. User inputs the command to delete a reminder +2. ClientHub deletes the reminder from the client +3. ClientHub shows successful output message + Use case ends. + +**Extensions** + +* 1a. ClientHub detects invalid input format + * 1a1. ClientHub shows an error message that the input is in the wrong format + + Step 1 is repeated until user inputs the correct format. +* 2a. ClientHub detects the given index is out of range + * 2a1. ClientHub shows an error message that the index is out of range + + Step 1 is repeated until user inputs the correct format. + + +**Use case: Edit reminder** + +**MSS** +1. User inputs the command to edit a reminder +2. ClientHub edits the reminder +3. ClientHub shows successful output message + Use case ends. + +**Extensions** + +* 1a. ClientHub detects invalid input format + * 1a1. ClientHub shows an error message that the input is in the wrong format + + Step 1 is repeated until user inputs the correct format. +* 2a. ClientHub detects the given index is out of range + * 2a1. ClientHub shows an error message that the index is out of range + + Step 1 is repeated until user inputs the correct format. ### Non-Functional Requirements 1. Should work on any _mainstream OS_ as long as it has Java `17` 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. +2. Should be able to hold up to 1000 client's client without a noticeable sluggishness in performance for typical usage. +3. Should be able to load client's client within 2 seconds to provide a smooth user experience. +4. As the number of clients increases, the app should be able to handle the increased data load without significant degradation in performance. +5. A financial advisor 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. +6. The application should have an intuitive and easy-to-navigate UI so that financial advisors can quickly find clients and input data without much training. +7. The system should be designed to easily accommodate new features or updates. -*{More to be added}* ### Glossary * **Mainstream OS**: Windows, Linux, Unix, MacOS -* **Private contact detail**: A contact detail that is not meant to be shared with others - +* **Private client Detail**: A client detail that is not meant to be shared with others +* **Client**: A person or company that is in the client list +* **Client Type**: A category used to describe the relationship or status of a client, such as VIP and standard. +* **client**: A client's information saved in the system, which includes details such as name, phone number, email, address, client type, and descriptions. +* **Financial Advisor**: The primary user of ClientHub, responsible for managing a large number of clients and using the system to track details, tasks, and interactions with clients. -------------------------------------------------------------------------------------------------------------------- ## **Appendix: Instructions for manual testing** @@ -341,42 +504,58 @@ testers are expected to do more *exploratory* testing. ### Launch and shutdown -1. Initial launch +#### 1. Initial launch - 1. Download the jar file and copy into an empty folder +* **For Mac** - 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. + 1. Download the jar file and copy into an empty folder called `ClientHub` in `Downloads`. + 2. Open Terminal + 3. Inside the terminal enter: `cd Downloads/ClientHub` + 4. Then enter:`java -jar clienthub.jar` + * Expected: Shows the GUI with a set of sample clients. + * 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. +* **For Windows** + + 1. Download the jar file and copy into an empty folder called `ClientHub` in `Downloads`. + 2. Open PowerShell + 3. Inside the Command Prompt enter: `cd Downloads/ClientHub` + 4. Then enter:`java -jar clienthub.jar` + * Expected: Shows the GUI with a set of sample clients. + * The window size may not be optimum. - 1. Re-launch the app by double-clicking the jar file.
+#### 2. Saving window preferences + 1. Resize the window to an optimum size. Move the window to a different location. Close the window. + 2. Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained. -1. _{ more test cases …​ }_ -### Deleting a person +### Deleting a contact -1. Deleting a person while all persons are being shown +1. Deleting a client while all clients are being shown - 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + 1. Prerequisites: List all clients using the `list` command. Multiple clients in the list. - 1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. + 1. Test case: `delete Ahmad`
+ Expected: Contact with name "Ahmad" is deleted. Details of the deleted client shown in the status message. Timestamp in the status bar is updated. - 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. + 1. Test case: `delete x` (where x is a number)
+ Expected: No client is deleted. Error details shown in the status message. Status bar remains the same. - 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
+ 1. Other incorrect delete commands to try: `delete` `delete [NAME]` (with a client not in the list)
Expected: Similar to previous. -1. _{ more test cases …​ }_ -### Saving data +1. Deleting a client with the same name + 1. Test case: `delete Jeremy` with more than one client with the name `Jeremy`
+ Expected: User is prompted to be more specific. Details of the required change are shown in the status message. Status bar remains the same. -1. Dealing with missing/corrupted data files +### Saving data - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ +1. Checking if changes in data are saved + 1. Use any of the data changing commands eg.`add` `delete` `edit`. + 2. Exit ClientHub with `exit` or closing the window. + 3. Re-launch the app by double-clicking the jar file.
+ Expected: Changes made previously are loaded into the displayed data -1. _{ more test cases …​ }_ diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 84b4ddc4e40..1d93f40b860 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -3,7 +3,7 @@ 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. +ClientHub 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, ClientHub can get your contact management tasks done faster than traditional GUI apps. * Table of Contents {:toc} @@ -14,11 +14,25 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo 1. Ensure you have Java `17` or above installed in your Computer. -1. Download the latest `.jar` file from [here](https://github.com/se-edu/addressbook-level3/releases). +1. Download the latest `.jar` file from [here](https://github.com/AY2425S1-CS2103T-F10-1/tp/releases). + +1. Copy the file to the folder you want to use as the _home folder_ for your ClientHub. + +Example: + +**For Mac** +1. If you have downloaded the jar file into a folder in your `Downloads` +2. Open Terminal +3. Type `cd Downloads/[FolderName]` and press Enter +4. Type `java -jar clienthub.jar` and press Enter + +**For Windows** +1. If you have downloaded the jar file into a folder in your `Downloads` +2. Open PowerShell +3. Type `cd Downloads/[FolderName]` and press Enter +4. Type `java -jar clienthub.jar` and press Enter -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. -1. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar addressbook.jar` command to run the application.
A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
![Ui](images/Ui.png) @@ -27,9 +41,9 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo * `list` : Lists all contacts. - * `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book. + * `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01 d/likes ramen` : Adds a contact named `John Doe` to the Client Hub. - * `delete 3` : Deletes the 3rd contact shown in the current list. + * `delete John Doe` : Deletes John Doe from the Client Hub. * `clear` : Deletes all contacts. @@ -49,14 +63,17 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo 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`. + e.g `n/NAME [c/CLIENT_TYPE]` can be used as `n/John Doe c/Plan A` or as `n/John Doe`. * 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. + e.g. `[c/CLIENT_TYPE]…​` can be used as ` ` (i.e. 0 times), `c/Plan A`, `c/Plan A c/Plan B` etc. * 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. +* Adding `$` after the input name is used to indicate **specific** name.
+ e.g. `delete John Doe$` will delete the contact with the name `John Doe`. + * Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
e.g. if the command specifies `help 123`, it will be interpreted as `help`. @@ -65,105 +82,412 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo ### Viewing help : `help` -Shows a message explaning how to access the help page. +Shows a message explaining how to access the help page. ![help message](images/helpMessage.png) Format: `help` -### Adding a person: `add` +### Adding a client: `add` + +Adds a client to Client Hub. + +Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS d/DESCRIPTION c/CLIENT_TYPE…​` + +A **valid** `NAME` should: +* Not be empty. + * For eg. Just typing `n/` without providing any `NAME` will throw an error. +* Can only have one name. + * For eg. Typing `n/John Doe n/John Eng` will throw an error. +* have **only letters** for the *first* character +* have **only letters** or **parenthesis** for the *last* character +* have **letters** or **parenthesis** or **slash** for the *middle* characters + * eg. `John Doe`, `John (NUS)`, `John S/O Bob` is **valid** + * eg. `John 123`, `!John Doe` is **invalid** + +A **valid** `PHONE_NUMBER` should: +* Only numbers are allowed. + * For eg. Typing `find p/abc` will throw an error. +* Not be empty. + * For eg. Just typing `p/` without providing any `PHONE_NUMBER` will throw an error. +* Only 8 digit phone numbers are allowed + * For eg. Typing `p/123456789` will throw an error. +* Can only have one phone number. + * For eg. Typing `p/12345678 p/12345678` will throw an error. + +A **valid** `EMAIL` should: +* Not be empty. + * For eg. Just typing `e/` without providing any `EMAIL` will throw an error. +* Can only have one email. + * For eg. Typing `e/abc@mail.com e/example@mail.com` will throw an error. +* A valid email should be of the format local-part@domain and adhere to the following constraints: +* The local-part should only contain alphanumeric characters and these special characters, excluding the parentheses, (+_.-). The local-part may not start or end with any special characters. +* This is followed by a '@' and then a domain name. The domain name is made up of domain labels separated by periods. + The domain name must: + - end with a domain label at least 2 characters long + - have each domain label start and end with alphanumeric characters + - have each domain label consist of alphanumeric characters, separated only by hyphens, if any. + +A **valid** `ADDRESS` should: +* Not be empty. + * For eg. Just typing `a/` without providing any `ADDRESS` will throw an error. +* Can only have one address. + * For eg. Typing `a/John street, block 123, #01-01 a/John street, block 123, #01-02` will throw an error. +* A valid address can only take in letters, numbers or the following characters: ,#-():; and it should not be blank. + +A **valid** `DESCRIPTION` should: +* Not be empty. + * For eg. Just typing `d/` without providing any `DESCRIPTION` will throw an error. + * Be limited to 500 characters + * For eg. Typing `d/Imagine this is a very long description that is more than 500 characters long` will throw an error. +* Can only have one description. + * For eg. Typing `d/likes bubble tea d/likes bubble tea` will throw an error. + +A **valid** `CLIENT_TYPE` should: +* Only be alphanumeric. Special Characters are not valid. (eg. Investment #1 is invalid) + * `CLIENT_TYPE` will always be in alphanumeric format. +* Not be empty. + * For eg. Just typing `c/` without providing any `CLIENT_TYPE` will throw an error. +* Client types names can have at most 30 characters (space inclusive). + * For eg. Typing `c/InvestmentPlanHealthcarePlanInsurancePlan` will throw an error. +* Can have multiple client types. + * For eg. Typing `c/Plan A c/Plan B` is valid. +* Not have duplicates. + * For eg. Typing `c/Plan A c/Plan A` will combine the client types into `Plan A`. + +Examples: +* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01 d/likes bubble tea c/Investment` +* `add n/Betsy Crowe p/1234567 e/betsycrowe@example.com a/Yishun Town d/Loves travelling c/Investment c/Healthcare ` -Adds a person to the address book. +Result for `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01 d/likes bubble tea c/Investment`: +![result for 'add'](images/result_for_add.png) -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` -
:bulb: **Tip:** -A person can have any number of tags (including 0) -
+### Editing a client: `edit` + +Edits an existing client in Client Hub. + +Format: `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [d/DESCRIPTION] [c/CLIENT_TYPE]…​` + +* Edits the client at the specified `INDEX`. The index refers to the index number shown in the displayed client list. The index **must be a positive integer** 1, 2, 3, …​ +* The fields constraints are the same as the `add` command. +* At least one of the optional fields must be provided. + * At most 1 of each field can be edited at a time.(excluding CLIENT_TYPE) +* Existing values will be updated to the input values. +* When editing `CLIENT_TYPE`, the existing `CLIENT_TYPE` of the person will be removed i.e adding of `CLIENT_TYPE` is not cumulative. +* When editing `NAME`, the client with the associated `NAME` should have **no** reminders. 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 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 c/investment c/healthcare` Edits the name of the 2nd person to be `Betsy Crower` and add 2 client types of `investment` and `healthcare`. -### Listing all persons : `list` +result for `edit 1 p/91234567`: +![result for 'edit 1 p/91234567'](images/result_for_edit.png) -Shows a list of all persons in the address book. -Format: `list` +### Deleting a client: `delete` -### Editing a person : `edit` +Deletes the specified person from ClientHub. -Edits an existing person in the address book. +Format: `delete NAME` or `d NAME` or `delete NAME$` -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +* Deletes the client with specified `NAME` +* `$` is used to indicate specific name to delete + * For eg. if 2 contacts have names such as "David Li" and "David Lim", typing `delete David Li$` will delete the contact with the name "David Li". + * However, deleting David Lim does not require `$` as it is already the **MOST** specific name. + * Name written before `$` must be **EXACT** name of the contact to be deleted. + * Order matters when using `$` to delete a contact. -* 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. +A **valid** `NAME` for delete should: +* Not be empty. +* For eg. Just typing `delete` without providing any `NAME` will throw an error. +* Be a valid name that exists in the list of contacts. +* For eg. Typing `delete John Doe` when there is no contact with the name `John Doe` will throw an error. + +Examples: +* `delete John Doe` deletes the person named `John Doe` +* `delete John Doe$` deletes the person named `John Doe` and not `John Doey` + +Result for `delete John Doe`: +![result for 'delete John Doe'](images/result_for_delete.png) + + +### Locating clients by key information: `find` + +Finds clients by `NAME`, `PHONE_NUMBER`, `ADDRESS` or `CLIENT_TYPE`. + +#### Locating by `NAME` +Format: `find n/NAME` or `fn NAME` or `find NAME$` + * Only the name is searched. + * 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` + * Prefix of words will be matched e.g. `Ha B` will match `Hans Bo` + * `$` is used to indicate exact name to find + * Clients matching all keyword prefix will be returned (i.e. `AND` search). + e.g. `Hans Bo` will return `Hans Bo` but not `Hans Gruber`, `Bo Yang` + +A **valid** `NAME` should: +* Not be empty. + * For eg. Just typing `find n/` without providing any `KEYWORD` will throw an error. +* Be a valid name that exists in the list of contacts. + * For eg. Typing `find n/John Doe` when there is no contact with the name `John Doe` will throw an error. + +Examples: +* `find n/John` returns `john` and `John Doe` +* `find n/alex yeo` returns `Alex Yeoh` +* `fn John` returns `John`, `John Doe`, `Doe John`, `Doe John Eng` +* `fn Ale Yeo` returns `Alex Yeoh` +* `fn Yeoh Alex` returns `Alex Yeoh` +* `fn aLex yEOh` returns `Alex Yeoh` + +Result for `find n/roy`: +![result for 'find n/roy'](images/result_for_find_name.png) + + +#### Locating by `PHONE_NUMBER` +Format: `find p/PHONE_NUMBER` or `fp PHONE_NUMBER` + * Only numbers that begin with keyword will be matched e.g. `8765432` will not match `98765432` + +A **valid** `PHONE_NUMBER` should: +* Only numbers are allowed. + * For eg. Typing `find p/abc` will throw an error. +* Not be empty. + * For eg. Just typing `find p/` without providing any `KEYWORD` will throw an error. + +Examples: +* `find p/9103` returns `91031282` +* `fp 8433` returns `8433 4567` + +Result for `find p/9103`: +![result for 'find 9103'](images/result_for_find_phone.png) + + +#### Locating by `ADDRESS` +Format: `find a/ADDRESS` or `fa ADDRESS` + * The search is case-insensitive. e.g `tampines` will match `Tampines` + * Only the address of the contact is searched. + * Clients with address with any matching substring to the keyword will be returned. + +A **valid** `ADDRESS` should: +* Not be empty. + * For eg. Just typing `find a/` without providing any `ADDRESS` will throw an error. + +Examples: +* `find a/Blk 47` returns `Blk 47 Tampines Street 20` +* `fa Blk 47` returns `Blk 47 Tampines Street 20` + +Result for `find a/tampines`: +![result for 'fa tampines`](images/result_for_find_address.png) + + +#### Locating by `CLIENT_TYPE` +Format: `find c/CLIENT_TYPE…​` or `fc CLIENT_TYPE…​` +* The search is case-insensitive. e.g `investment` will match `Investment` +* Only the `CLIENT_TYPE` of the person is searched. +* Clients whose `CLIENT_TYPE` contains a substring that matches the provided `CLIENT_TYPE` will be returned. +* Client with `CLIENT_TYPE` that has a prefix matching the input `CLIENT_TYPE` will be returned (i.e. `AND` search). +* Duplicate `CLIENT_TYPE` will be combined into 1 (No way to have duplicate client types showing) + +A **valid** `CLIENT_TYPE` should: +* Only be alphanumeric. Special Characters are not valid. (eg. Investment #1 is invalid) + * `client_type` will always be in alphanumeric format. +* Not be empty. + * For eg. Just typing `find c/` without providing any `CLIENT_TYPE` will throw an error. +* Not have duplicates. + * For eg. Typing `c/Plan A Plan A` will combine the client types into `Plan A`. + +Examples: +* `find c/Investment` returns every contact that has a `client_type` beginning with `Investment` +* `find c/Invest` returns every contact that has `client_type` beginning with `Invest` +* `find c/Investment Healthcare` returns every contact that has `client_type` beginning with `Investment` and `Healthcare` +* `fc Investment` returns `Investment Plan` +* `fc Investment Healthcare` returns `Investment Plan` and `Healthcare Plan` + +Result for `find c/Investment`: +![result for 'find c/Investment`](images/result_for_find_clienttypes.png) + + +### Reminder Features +ClientHub has a basic reminder list that keeps track of a users commitments to +specific clients. The reminder list is a list of reminders that can be added, +deleted, and edited. + + +#### Adding Reminder: `radd` + +Adds a reminder to the reminder list. + +Format: +`radd n/NAME dt/DATETIME r/REMINDER_DESCRIPTION` or +`ra n/NAME dt/DATETIME r/REMINDER_DESCRIPTION` + +A **valid** `NAME` for add should: +* Not be empty. +* Be a valid name that exists in the list of clients. +* For eg. Typing `radd John Doe` when there is no client with the name `John Doe` will throw an error. +* Be a prefix match of the client name. + * `n/John Doe` will **add a reminder** for `John Doe` if there is `John Doe` and `John Doey` in the contact list. + * `n/John Doe` will **throw an error** if there is `John Doe` and `John Doey` in the contact list. + * to add a reminder for `John Doe`, type `radd John Doe$` + +A **valid** `DATETIME` for add should: +* Not be empty. +* Be a valid date and time in the format `yyyy-MM-dd HH:mm`. + * Time should be in 24-hour format. +* For eg. Typing `radd n/John Doe dt/2022-10-10 12:00 d/lunch` will add a reminder for `John Doe` for `lunch` at `2022-10-10 12:00`. + +A **valid** `REMINDER_DESCRIPTION` for add should: +* Not be empty. +* Be limited to 300 characters +* For eg. Typing `radd n/John Doe dt/2022-10-10 12:00 d/` will throw an error. +* For eg. Typing `radd n/John Doe dt/2022-10-10 12:00 d/Meeting with John at 12pm` will add a reminder for `John Doe` for `Meeting with John at 12pm` at `2022-10-10 12:00`. + +Result for `radd n/John Doe dt/2022-10-10 12:00 d/lunch`: +![result for 'radd`](images/result_for_add_reminder.png) + + +#### Editing a Reminder: `redit` + +Edits an existing reminder in the reminder list. + +Format: +`redit INDEX [dt/DATETIME] [d/REMINDER_DESCRIPTION]` or +`re INDEX [dt/DATETIME] [d/REMINDER_DESCRIPTION]` + +* Edits the reminder at the specified `INDEX`. The index refers to the index number shown in the displayed reminder list. The index **must be a positive integer** 1, 2, 3, …​ +* Have least one of the optional fields must be provided. + * At most 1 of each field can be edited at a time. * 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. + +A **valid** `INDEX` for edit should: +* Not be empty. +* For eg. Just typing `edit` without providing any `INDEX` will throw an error. +* Be a valid index that exists in the list of contacts. +* For eg. Typing `redit 1` when there is no contact at index 1 will throw an error + +A **valid** `DATETIME` for edit should: +* Be a valid date and time in the format `yyyy-MM-dd HH:mm`. + * Time should be in 24-hour format. +* For eg. Typing `redit 1 dt/2022-10-10 12:00` will edit the `DATETIME` of the reminder at index `1` to `2022-10-10 12:00`. + +A **valid** `REMINDER_DESCRIPTION` for edit should: +* Be limited to 300 characters +* For eg. Typing `redit 1 d/Meeting with John at 12pm` will edit the `REMINDER_DESCRIPTION` of the reminder at index `1` to `Meeting with John at 12pm`. + 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. +* `redit 1 dt/2022-10-10 12:00 d/Meeting for lunch` Edits the date and time and description of the 1st reminder to be `2022-10-10 12:00` and `Meeting for lunch` respectively. +* `re 2 dt/2022-10-10 12:00` Edits the date and time of the 2nd reminder to be `2022-10-10 12:00` + +Result for `redit 1 dt/2022-10-10 12:00 d/Meeting for lunch`: +![result for 'redit`](images/result_for_edit_reminder.png) -### Locating persons by name: `find` -Finds persons whose names contain any of the given keywords. +#### Deleting a Reminder: `rdelete` -Format: `find KEYWORD [MORE_KEYWORDS]` +Deletes a reminder from the reminder 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` +Format: +`rdelete INDEX` or +`rd INDEX` + +* Deletes the person with specified INDEX. The index refers to the index number shown in the displayed reminder list. The index **must be a positive integer** 1, 2, 3, …​ + +A **valid** `INDEX` for delete should: +* Not be empty. +* For eg. Just typing `delete` without providing any `INDEX` will throw an error. +* Be a valid index that exists in the list of contacts. +* For eg. Typing `rdelete 1` when there is no contact at index 1 will throw an error Examples: -* `find John` returns `john` and `John Doe` -* `find alex david` returns `Alex Yeoh`, `David Li`
- ![result for 'find alex david'](images/findAlexDavidResult.png) +* `rdelete 1` deletes the person at index 1 of the list +* `rd 2` will delete the person at index 2 of the list + +Result for `rdelete 1`: +![result for 'rdelete`](images/result_for_delete_reminder.png) -### Deleting a person : `delete` -Deletes the specified person from the address book. +### Viewing a client: `view` -Format: `delete INDEX` +Creates a popup view of the specified client from ClientHub. -* 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, …​ +Format: `view NAME` or `v NAME` or `view NAME$` +* The command is case-insensitive. eg. `alice` will match `Alice` +* The command does a `find` and displays the popup view only if the no. of clients found is exactly 1. +* If duplicates are found, `view` will throw an error telling user to specify the name further. + * For eg. if 2 contacts have names such as "David Li" and "David Lim", typing `view David` will throw an error. +* `$` is used to indicate **specific** name to view + * If there are two contacts named `David Li` and `David Lim`, typing `view David Li$` will show the contact with the name `David Li`. + * For contacts with names that are already unique, like `David Lim`, the `$` is not required. + * The `NAME` before the `$` must match the contact's name **exactly**. + * The order of the `NAME` and `$` matters - `David Li$` is different from `Li$David`. + +A **valid** `NAME` for view should: +* Not be empty. + * For eg. Just typing `view` without providing any `NAME` will throw an error. +* Be a valid name that exists in the list of contacts. + * For eg. Typing `view John Doe` when there is no contact with the name `John Doe` will throw an error. +* Be a prefix match of the contact name. + * Typing `view John` will **throw an error** if there is `John Doe` and `John Doey` in the contact list. + * Typing `view John` if there is only `John Doe` in the contact list will **create a popup view** of `John Doe`. 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. +* `view John Doe` shows the contact named `John Doe` +* `view John` will throw an error if there is `John Doe` and `John Doey` in the list of contacts. -### Clearing all entries : `clear` +Result for `view jeremy`: +![result for 'view jeremy`](images/result_for_view.png) + + +### Sort by name : `sort` +Sort the current list of clients on Client Hub according to their `NAME`. + +Format: `sort` + +* The list of clients is sorted alphabetically by their full names in ascending order, where no duplicate names are allowed. + +Examples: +* `sort` sorts the list -Clears all entries from the address book. + +### Listing all persons : `list` +Shows a list of all persons in the Client Hub. + +Format: `list` + + +### Clearing all entries : `clear` +Clears all entries from ClientHub. Format: `clear` ### Exiting the program : `exit` - Exits the program. Format: `exit` +## Additional Features +### Command History Navigation +The command history feature allows users to easily navigate through previously entered commands using the **Up** and **Down** arrow keys. This is helpful for quickly accessing past commands without needing to retype them. + +* **Up** Arrow: Moves to the previous command in the history. +* **Down** Arrow: Moves to the next command in the history. + +This feature enables efficient command recall, streamlining the process of repeating or editing previous inputs. + ### Saving the data -AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +ClientHub data is saved in the hard disk automatically after any command that changes +the data. There is no need to save manually. ### Editing the data file -AddressBook data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. +ClientHub data is saved automatically as a JSON file `[JAR file location]/data/clienthub.json`. Advanced users are welcome to update data directly by editing that data file.
:exclamation: **Caution:** -If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it.
-Furthermore, certain edits can cause the AddressBook to behave in unexpected ways (e.g., if a value entered is outside of the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly. +If your changes to the data file makes its format invalid, ClientHub will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it.
+Furthermore, certain edits can cause the ClientHub to behave in unexpected ways (e.g., if a value entered is outside of the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly.
### Archiving data files `[coming in v2.0]` @@ -175,7 +499,7 @@ _Details coming soon ..._ ## FAQ **Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder. +**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous ClientHub home folder. -------------------------------------------------------------------------------------------------------------------- @@ -188,12 +512,21 @@ _Details coming soon ..._ ## 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` +| Action | Format, Examples | +|-----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Help | `help` | +| **Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS c/CLIENT_TYPE d/DESCRIPTION…​ `
e.g `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 c/Plan A c/Plan A d/crimefighter` | +| **Delete** | `delete NAME`
e.g `delete JAMES` | +| **Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [c/CLIENT_TYPE] [d/DESCRIPTION]`
e.g `edit 2 n/James Lee e/jameslee@example.com` | +| **Find Name** | `find n/NAME` or `fn NAME`
e.g `find n/John` or `fn John` | +| **Find Phone Number** | `find p/PHONE_NUMBER` or `fp PHONE_NUMBER`
e.g `find p/98765432` or `fp 987654432` | +| **Find Address** | `find a/ADDRESS` or `fa ADDRESS`
e.g `find a/Blk 30` or `fa Blk 30` | +| **Find Client Type** | `find c/CLIENT_TYPE` or `fc CLIENT_TYPE`
e.g `find c/Investment` or `fc Investment` | +| **Add Reminder** | `radd n/NAME dt/DATETIME d/DESCRIPTION` or `ra /NAME dt/DATETIME d/DESCRIPTION`
e.g `radd n/John dt/2024-01-01 12:00 d/Appointment` or `ra n/John dt/2024-01-01 12:00 d/Appointment` | +| **Delete Reminder** | `delete INDEX` or `rd INDEX`
e.g `delete 1` or `rd INDEX` | +| **Edit Reminder** | `redit INDEX [dt/DATETIME] [d/DESCRIPTION]` or `re INDEX [dt/DATETIME] [d/DESCRIPTION]`
e.g `redit 1 [dt/2024-02-02 13:00] [d/Review]` or `re 1 [dt/2024-02-02 13:00] [d/Review]` | +| **View** | `view NAME`
e.g., `view James` | +| **Sort** | `sort` | +| **List** | `list` | +| **CLear** | `clear` | +| **Exit** | `exit` | diff --git a/docs/_config.yml b/docs/_config.yml index 6bd245d8f4e..eaf0869fa17 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,4 +1,4 @@ -title: "AB-3" +title: "ClientHub" theme: minima header_pages: @@ -8,7 +8,7 @@ header_pages: markdown: kramdown -repository: "se-edu/addressbook-level3" +repository: "AY2425S1-CS2103T-F10-1/tp" github_icon: "images/github-icon.png" plugins: diff --git a/docs/diagrams/AddReminderCommandActivityDiagram.puml b/docs/diagrams/AddReminderCommandActivityDiagram.puml new file mode 100644 index 00000000000..490f9a3483e --- /dev/null +++ b/docs/diagrams/AddReminderCommandActivityDiagram.puml @@ -0,0 +1,19 @@ +@startuml +skin rose +skinparam ActivityFontSize 15 +skinparam ArrowFontSize 12 +start +:User executes AddReminder command; + +'Since the beta syntax does not support placing the condition outside the +'diamond we place it as the true branch instead. + +if () then ([client is in ClientHub]) + :Retrieve Client from ClientHub; + :Add reminder to Client; + :Add reminder to ReminderList; + +else ([else]) +endif +stop +@enduml diff --git a/docs/diagrams/AddReminderSequenceDiagram-Logic.puml b/docs/diagrams/AddReminderSequenceDiagram-Logic.puml new file mode 100644 index 00000000000..a07e90b3542 --- /dev/null +++ b/docs/diagrams/AddReminderSequenceDiagram-Logic.puml @@ -0,0 +1,46 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":ClientHubParser" as ClientHubParser LOGIC_COLOR +participant "ar:AddReminderCommand" as AddReminderCommand LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box +[-> LogicManager : execute("radd n/Alice ...") +activate LogicManager + +LogicManager -> ClientHubParser : parseCommand("radd n/Alice ...") +activate ClientHubParser + +create AddReminderCommand +ClientHubParser -> AddReminderCommand +activate AddReminderCommand + +AddReminderCommand --> ClientHubParser +deactivate AddReminderCommand + +ClientHubParser --> LogicManager : u +deactivate ClientHubParser + +LogicManager -> AddReminderCommand : execute() +activate AddReminderCommand + +AddReminderCommand -> Model : addReminder() +activate Model + +Model --> AddReminderCommand +deactivate Model + +AddReminderCommand --> LogicManager : result +deactivate AddReminderCommand +AddReminderCommand -[hidden]-> LogicManager : result +destroy AddReminderCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/AddReminderSequenceDiagram-Model.puml b/docs/diagrams/AddReminderSequenceDiagram-Model.puml new file mode 100644 index 00000000000..e85b6f9eea8 --- /dev/null +++ b/docs/diagrams/AddReminderSequenceDiagram-Model.puml @@ -0,0 +1,28 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant ":ClientHub" as ClientHub MODEL_COLOR +participant ":ReminderList" as ReminderList MODEL_COLOR +end box + +[-> Model : addReminder(reminder) +activate Model + +Model -> ClientHub : addReminder(reminder) +activate ClientHub + +ClientHub -> ReminderList :add(reminder) +activate ReminderList +ReminderList --> ClientHub : +deactivate ReminderList + +ClientHub --> Model : +deactivate ClientHub + +[<-- Model +deactivate Model + +@enduml diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index 48b6cc4333c..90ebe42d58d 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -8,10 +8,10 @@ Participant ":Logic" as logic LOGIC_COLOR Participant ":Model" as model MODEL_COLOR Participant ":Storage" as storage STORAGE_COLOR -user -[USER_COLOR]> ui : "delete 1" +user -[USER_COLOR]> ui : "delete Alice" activate ui UI_COLOR -ui -[UI_COLOR]> logic : execute("delete 1") +ui -[UI_COLOR]> logic : execute("delete Alice") activate logic LOGIC_COLOR logic -[LOGIC_COLOR]> model : deletePerson(p) @@ -20,7 +20,7 @@ activate model MODEL_COLOR model -[MODEL_COLOR]-> logic deactivate model -logic -[LOGIC_COLOR]> storage : saveAddressBook(addressBook) +logic -[LOGIC_COLOR]> storage : saveClientHub(clientHub) activate storage STORAGE_COLOR storage -[STORAGE_COLOR]> storage : Save to file diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml index 598474a5c82..222e938781d 100644 --- a/docs/diagrams/BetterModelClassDiagram.puml +++ b/docs/diagrams/BetterModelClassDiagram.puml @@ -4,18 +4,23 @@ skinparam arrowThickness 1.1 skinparam arrowColor MODEL_COLOR skinparam classBackgroundColor MODEL_COLOR -AddressBook *-right-> "1" UniquePersonList -AddressBook *-right-> "1" UniqueTagList -UniqueTagList -[hidden]down- UniquePersonList -UniqueTagList -[hidden]down- UniquePersonList +ClientHub *-right-> "1" UniquePersonList +ClientHub *-right-> "1" UniqueClientTypeList +ClientHub *-right-> "1" ReminderList +UniqueClientTypeList -[hidden]down- UniquePersonList +UniqueClientTypeList -[hidden]down- UniquePersonList +ReminderList -[hidden]down- UniqueClientTypeList -UniqueTagList -right-> "*" Tag +UniqueClientTypeList -right-> "*" ClientType UniquePersonList -right-> Person +ReminderList -right-> Reminder -Person -up-> "*" Tag +Person -up-> "*" ClientType Person *--> Name Person *--> Phone Person *--> Email Person *--> Address +Person *--> Description +Person *--> Reminder @enduml diff --git a/docs/diagrams/CommitActivityDiagram.puml b/docs/diagrams/CommitActivityDiagram.puml index 8c0892d6a70..dc0b08cc159 100644 --- a/docs/diagrams/CommitActivityDiagram.puml +++ b/docs/diagrams/CommitActivityDiagram.puml @@ -8,9 +8,9 @@ start 'Since the beta syntax does not support placing the condition outside the 'diamond we place it as the true branch instead. -if () then ([command commits AddressBook]) +if () then ([command commits ClientHub]) :Purge redundant states; - :Save AddressBook to + :Save ClientHub to addressBookStateList; else ([else]) endif diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 5241e79d7da..b04a28739aa 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -4,7 +4,7 @@ skinparam ArrowFontStyle plain box Logic LOGIC_COLOR_T1 participant ":LogicManager" as LogicManager LOGIC_COLOR -participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":ClientHubParser" as AddressBookParser LOGIC_COLOR participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR participant "r:CommandResult" as CommandResult LOGIC_COLOR @@ -14,10 +14,10 @@ box Model MODEL_COLOR_T1 participant "m:Model" as Model MODEL_COLOR end box -[-> LogicManager : execute("delete 1") +[-> LogicManager : execute("delete Alice") activate LogicManager -LogicManager -> AddressBookParser : parseCommand("delete 1") +LogicManager -> AddressBookParser : parseCommand("delete Alice") activate AddressBookParser create DeleteCommandParser @@ -27,7 +27,7 @@ activate DeleteCommandParser DeleteCommandParser --> AddressBookParser deactivate DeleteCommandParser -AddressBookParser -> DeleteCommandParser : parse("1") +AddressBookParser -> DeleteCommandParser : parse("Alice") activate DeleteCommandParser create DeleteCommand @@ -49,7 +49,7 @@ deactivate AddressBookParser LogicManager -> DeleteCommand : execute(m) activate DeleteCommand -DeleteCommand -> Model : deletePerson(1) +DeleteCommand -> Model : deletePerson(Alice) activate Model Model --> DeleteCommand diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 0de5673070d..fc0f256a31a 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -5,20 +5,23 @@ skinparam arrowColor MODEL_COLOR skinparam classBackgroundColor MODEL_COLOR Package Model as ModelPackage <>{ -Class "<>\nReadOnlyAddressBook" as ReadOnlyAddressBook +Class "<>\nReadOnlyClientHub" as ReadOnlyClientHub Class "<>\nReadOnlyUserPrefs" as ReadOnlyUserPrefs Class "<>\nModel" as Model -Class AddressBook +Class ClientHub Class ModelManager Class UserPrefs Class UniquePersonList +Class ReminderList Class Person Class Address Class Email Class Name Class Phone -Class Tag +Class ClientType +Class Description +Class Reminder Class I #FFFFFF } @@ -26,22 +29,26 @@ Class I #FFFFFF Class HiddenOutside #FFFFFF HiddenOutside ..> Model -AddressBook .up.|> ReadOnlyAddressBook +ClientHub .up.|> ReadOnlyClientHub ModelManager .up.|> Model Model .right.> ReadOnlyUserPrefs -Model .left.> ReadOnlyAddressBook -ModelManager -left-> "1" AddressBook +Model .left.> ReadOnlyClientHub +ModelManager -left-> "1" ClientHub ModelManager -right-> "1" UserPrefs UserPrefs .up.|> ReadOnlyUserPrefs -AddressBook *--> "1" UniquePersonList +ClientHub *--> "1" UniquePersonList +ClientHub *--> "1" ReminderList +ReminderList *--> "~* all" Reminder UniquePersonList --> "~* all" Person Person *--> Name Person *--> Phone Person *--> Email Person *--> Address -Person *--> "*" Tag +Person *--> "*" ClientType +Person *--> Description +Person *--> Reminder Person -[hidden]up--> I UniquePersonList -[hidden]right-> I @@ -50,5 +57,6 @@ Name -[hidden]right-> Phone Phone -[hidden]right-> Address Address -[hidden]right-> Email -ModelManager --> "~* filtered" Person +ModelManager --> "~*" Person +ModelManager --> "~*" Reminder @enduml diff --git a/docs/diagrams/ParserClasses.puml b/docs/diagrams/ParserClasses.puml index ce4c5ce8c8d..9576e73c194 100644 --- a/docs/diagrams/ParserClasses.puml +++ b/docs/diagrams/ParserClasses.puml @@ -9,7 +9,7 @@ Class XYZCommand package "Parser classes"{ Class "<>\nParser" as Parser -Class AddressBookParser +Class ClientHubParser Class XYZCommandParser Class CliSyntax Class ParserUtil @@ -19,12 +19,12 @@ Class Prefix } Class HiddenOutside #FFFFFF -HiddenOutside ..> AddressBookParser +HiddenOutside ..> ClientHubParser -AddressBookParser .down.> XYZCommandParser: <> +ClientHubParser .down.> XYZCommandParser: <> XYZCommandParser ..> XYZCommand : <> -AddressBookParser ..> Command : <> +ClientHubParser ..> Command : <> XYZCommandParser .up.|> Parser XYZCommandParser ..> ArgumentMultimap XYZCommandParser ..> ArgumentTokenizer diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index a821e06458c..89a9df29cf2 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -14,12 +14,13 @@ Class JsonUserPrefsStorage Class "<>\nStorage" as Storage Class StorageManager -package "AddressBook Storage" #F4F6F6{ -Class "<>\nAddressBookStorage" as AddressBookStorage -Class JsonAddressBookStorage -Class JsonSerializableAddressBook +package "ClientHub Storage" #F4F6F6{ +Class "<>\nClientHubStorage" as ClientHubStorage +Class JsonClientHubStorage +Class JsonSerializableClientHub Class JsonAdaptedPerson -Class JsonAdaptedTag +Class JsonAdaptedClientType +Class JsonAdaptedReminder } } @@ -29,15 +30,17 @@ HiddenOutside ..> Storage StorageManager .up.|> Storage StorageManager -up-> "1" UserPrefsStorage -StorageManager -up-> "1" AddressBookStorage +StorageManager -up-> "1" ClientHubStorage Storage -left-|> UserPrefsStorage -Storage -right-|> AddressBookStorage +Storage -right-|> ClientHubStorage JsonUserPrefsStorage .up.|> UserPrefsStorage -JsonAddressBookStorage .up.|> AddressBookStorage -JsonAddressBookStorage ..> JsonSerializableAddressBook -JsonSerializableAddressBook --> "*" JsonAdaptedPerson -JsonAdaptedPerson --> "*" JsonAdaptedTag +JsonClientHubStorage .up.|> ClientHubStorage +JsonClientHubStorage ..> JsonSerializableClientHub +JsonSerializableClientHub --> "*" JsonAdaptedPerson +JsonSerializableClientHub --> "*" JsonAdaptedReminder +JsonAdaptedPerson --> "*" JsonAdaptedClientType +JsonAdaptedPerson --> "*" JsonAdaptedReminder @enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 95473d5aa19..48904413913 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -15,6 +15,9 @@ Class PersonListPanel Class PersonCard Class StatusBarFooter Class CommandBox +Class ViewPersonWindow +Class ReminderListPanel +Class ReminderCard } package Model <> { @@ -35,8 +38,12 @@ MainWindow *-down-> "1" ResultDisplay MainWindow *-down-> "1" PersonListPanel MainWindow *-down-> "1" StatusBarFooter MainWindow --> "0..1" HelpWindow +MainWindow --> "0..1" ViewPersonWindow +MainWindow *-down-> "1" ReminderListPanel + PersonListPanel -down-> "*" PersonCard +ReminderListPanel -down-> "*" ReminderCard MainWindow -left-|> UiPart @@ -46,7 +53,10 @@ PersonListPanel --|> UiPart PersonCard --|> UiPart StatusBarFooter --|> UiPart HelpWindow --|> UiPart +ViewPersonWindow -left-|> UiPart +ReminderListPanel --|> UiPart +ReminderCard ..> Model PersonCard ..> Model UiManager -right-> Logic MainWindow -left-> Logic diff --git a/docs/diagrams/UndoRedoState0.puml b/docs/diagrams/UndoRedoState0.puml index 43a45903ac9..f835e75a6c2 100644 --- a/docs/diagrams/UndoRedoState0.puml +++ b/docs/diagrams/UndoRedoState0.puml @@ -7,9 +7,9 @@ skinparam ClassBackgroundColor #FFFFAA title Initial state package States { - class State1 as "ab0:AddressBook" - class State2 as "ab1:AddressBook" - class State3 as "ab2:AddressBook" + class State1 as "ch:ClientHub" + class State2 as "ch1:ClientHub" + class State3 as "ch3:ClientHub" } State1 -[hidden]right-> State2 State2 -[hidden]right-> State3 diff --git a/docs/diagrams/UndoRedoState1.puml b/docs/diagrams/UndoRedoState1.puml index 5a41e9e1651..0655c534533 100644 --- a/docs/diagrams/UndoRedoState1.puml +++ b/docs/diagrams/UndoRedoState1.puml @@ -7,9 +7,9 @@ skinparam ClassBackgroundColor #FFFFAA title After command "delete 5" package States <> { - class State1 as "ab0:AddressBook" - class State2 as "ab1:AddressBook" - class State3 as "ab2:AddressBook" + class State1 as "ch0:ClientHub" + class State2 as "ch1:ClientHub" + class State3 as "ch2:ClientHub" } State1 -[hidden]right-> State2 diff --git a/docs/diagrams/UndoRedoState2.puml b/docs/diagrams/UndoRedoState2.puml index ad32fce1b0b..8c4cfeb502b 100644 --- a/docs/diagrams/UndoRedoState2.puml +++ b/docs/diagrams/UndoRedoState2.puml @@ -7,9 +7,9 @@ skinparam ClassBackgroundColor #FFFFAA title After command "add n/David" package States <> { - class State1 as "ab0:AddressBook" - class State2 as "ab1:AddressBook" - class State3 as "ab2:AddressBook" + class State1 as "ch0:ClientHub" + class State2 as "ch1:ClientHub" + class State3 as "ch2:ClientHub" } State1 -[hidden]right-> State2 diff --git a/docs/diagrams/UndoRedoState3.puml b/docs/diagrams/UndoRedoState3.puml index 9187a690036..a8eac639129 100644 --- a/docs/diagrams/UndoRedoState3.puml +++ b/docs/diagrams/UndoRedoState3.puml @@ -7,9 +7,9 @@ skinparam ClassBackgroundColor #FFFFAA title After command "undo" package States <> { - class State1 as "ab0:AddressBook" - class State2 as "ab1:AddressBook" - class State3 as "ab2:AddressBook" + class State1 as "ch0:ClientHub" + class State2 as "ch1:ClientHub" + class State3 as "ch2:ClientHub" } State1 -[hidden]right-> State2 diff --git a/docs/diagrams/UndoRedoState4.puml b/docs/diagrams/UndoRedoState4.puml index 2bc631ffcd0..ab2144dd96c 100644 --- a/docs/diagrams/UndoRedoState4.puml +++ b/docs/diagrams/UndoRedoState4.puml @@ -7,9 +7,9 @@ skinparam ClassBackgroundColor #FFFFAA title After command "list" package States <> { - class State1 as "ab0:AddressBook" - class State2 as "ab1:AddressBook" - class State3 as "ab2:AddressBook" + class State1 as "ch0:ClientHub" + class State2 as "ch1:ClientHub" + class State3 as "ch2:ClientHub" } State1 -[hidden]right-> State2 diff --git a/docs/diagrams/UndoRedoState5.puml b/docs/diagrams/UndoRedoState5.puml index e77b04104aa..ceb8bf2c763 100644 --- a/docs/diagrams/UndoRedoState5.puml +++ b/docs/diagrams/UndoRedoState5.puml @@ -7,9 +7,9 @@ skinparam ClassBackgroundColor #FFFFAA title After command "clear" package States <> { - class State1 as "ab0:AddressBook" - class State2 as "ab1:AddressBook" - class State3 as "ab3:AddressBook" + class State1 as "ch0:ClientHub" + class State2 as "ch1:ClientHub" + class State3 as "ch3:ClientHub" } State1 -[hidden]right-> State2 diff --git a/docs/diagrams/UndoSequenceDiagram-Logic.puml b/docs/diagrams/UndoSequenceDiagram-Logic.puml index e57368c5159..6c7dc5f9d91 100644 --- a/docs/diagrams/UndoSequenceDiagram-Logic.puml +++ b/docs/diagrams/UndoSequenceDiagram-Logic.puml @@ -4,7 +4,7 @@ skinparam ArrowFontStyle plain box Logic LOGIC_COLOR_T1 participant ":LogicManager" as LogicManager LOGIC_COLOR -participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":ClientHubParser" as AddressBookParser LOGIC_COLOR participant "u:UndoCommand" as UndoCommand LOGIC_COLOR end box @@ -30,7 +30,7 @@ deactivate AddressBookParser LogicManager -> UndoCommand : execute() activate UndoCommand -UndoCommand -> Model : undoAddressBook() +UndoCommand -> Model : undoClientHub() activate Model Model --> UndoCommand diff --git a/docs/diagrams/UndoSequenceDiagram-Model.puml b/docs/diagrams/UndoSequenceDiagram-Model.puml index 54d83208cb8..80c8f4b71a7 100644 --- a/docs/diagrams/UndoSequenceDiagram-Model.puml +++ b/docs/diagrams/UndoSequenceDiagram-Model.puml @@ -4,18 +4,18 @@ skinparam ArrowFontStyle plain box Model MODEL_COLOR_T1 participant ":Model" as Model MODEL_COLOR -participant ":VersionedAddressBook" as VersionedAddressBook MODEL_COLOR +participant ":VersionedClientHub" as VersionedClientHub MODEL_COLOR end box -[-> Model : undoAddressBook() +[-> Model : undoClientHub() activate Model -Model -> VersionedAddressBook : undo() -activate VersionedAddressBook +Model -> VersionedClientHub : undo() +activate VersionedClientHub -VersionedAddressBook -> VersionedAddressBook :resetData(ReadOnlyAddressBook) -VersionedAddressBook --> Model : -deactivate VersionedAddressBook +VersionedClientHub -> VersionedClientHub :resetData(ReadOnlyClientHub) +VersionedClientHub --> Model : +deactivate VersionedClientHub [<-- Model deactivate Model diff --git a/docs/diagrams/tracing/LogicSequenceDiagram.puml b/docs/diagrams/tracing/LogicSequenceDiagram.puml index 42bf46d3ce8..d1cdf269246 100644 --- a/docs/diagrams/tracing/LogicSequenceDiagram.puml +++ b/docs/diagrams/tracing/LogicSequenceDiagram.puml @@ -3,7 +3,7 @@ skinparam ArrowFontStyle plain Participant ":LogicManager" as logic LOGIC_COLOR -Participant ":AddressBookParser" as abp LOGIC_COLOR +Participant ":ClientHubParser" as abp LOGIC_COLOR Participant ":EditCommandParser" as ecp LOGIC_COLOR Participant "command:EditCommand" as ec LOGIC_COLOR diff --git a/docs/images/AddReminderCommandActivity.png b/docs/images/AddReminderCommandActivity.png new file mode 100644 index 00000000000..5804613b9ee Binary files /dev/null and b/docs/images/AddReminderCommandActivity.png differ diff --git a/docs/images/AddReminderSequenceDiagram-Logic.png b/docs/images/AddReminderSequenceDiagram-Logic.png new file mode 100644 index 00000000000..7c9582d39e4 Binary files /dev/null and b/docs/images/AddReminderSequenceDiagram-Logic.png differ diff --git a/docs/images/AddReminderSequenceDiagram-Model.png b/docs/images/AddReminderSequenceDiagram-Model.png new file mode 100644 index 00000000000..cf8906addb3 Binary files /dev/null and b/docs/images/AddReminderSequenceDiagram-Model.png differ diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png index 37ad06a2803..692ce8713ef 100644 Binary files a/docs/images/ArchitectureSequenceDiagram.png and b/docs/images/ArchitectureSequenceDiagram.png differ diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png index 02a42e35e76..11ac43610ea 100644 Binary files a/docs/images/BetterModelClassDiagram.png and b/docs/images/BetterModelClassDiagram.png differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png index ac2ae217c51..e49faa1d164 100644 Binary files a/docs/images/DeleteSequenceDiagram.png and b/docs/images/DeleteSequenceDiagram.png differ diff --git a/docs/images/Find9103Result.png b/docs/images/Find9103Result.png new file mode 100644 index 00000000000..a9a04590ced Binary files /dev/null and b/docs/images/Find9103Result.png differ diff --git a/docs/images/FindInvestmentResult.png b/docs/images/FindInvestmentResult.png new file mode 100644 index 00000000000..fdedaa0fc76 Binary files /dev/null and b/docs/images/FindInvestmentResult.png differ diff --git a/docs/images/FindRoyResult.png b/docs/images/FindRoyResult.png new file mode 100644 index 00000000000..51404f76766 Binary files /dev/null and b/docs/images/FindRoyResult.png differ diff --git a/docs/images/FindTampinesResult.png b/docs/images/FindTampinesResult.png new file mode 100644 index 00000000000..e3c6edf9ffd Binary files /dev/null and b/docs/images/FindTampinesResult.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index a19fb1b4ac8..e212a5a1b0e 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/ParserClasses.png b/docs/images/ParserClasses.png index 2caeeb1a067..36ba62f2440 100644 Binary files a/docs/images/ParserClasses.png and b/docs/images/ParserClasses.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 18fa4d0d51f..cfcba0c55e3 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..de3eed08711 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 11f06d68671..109234fe8c5 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/UndoSequenceDiagram-Logic.png b/docs/images/UndoSequenceDiagram-Logic.png index 78e95214294..940169599e4 100644 Binary files a/docs/images/UndoSequenceDiagram-Logic.png and b/docs/images/UndoSequenceDiagram-Logic.png differ diff --git a/docs/images/ViewCommandExample.png b/docs/images/ViewCommandExample.png new file mode 100644 index 00000000000..07547c9671a Binary files /dev/null and b/docs/images/ViewCommandExample.png differ diff --git a/docs/images/ahmadsyuaib.png b/docs/images/ahmadsyuaib.png new file mode 100644 index 00000000000..35ad1988c7d Binary files /dev/null and b/docs/images/ahmadsyuaib.png differ diff --git a/docs/images/findAlexDavidResult.png b/docs/images/findAlexDavidResult.png deleted file mode 100644 index 235da1c273e..00000000000 Binary files a/docs/images/findAlexDavidResult.png and /dev/null differ diff --git a/docs/images/harithhh06.png b/docs/images/harithhh06.png new file mode 100644 index 00000000000..1fbf11ab4ef Binary files /dev/null and b/docs/images/harithhh06.png differ diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png index b1f70470137..720aa6e9360 100644 Binary files a/docs/images/helpMessage.png and b/docs/images/helpMessage.png differ diff --git a/docs/images/jereeemyyyy.png b/docs/images/jereeemyyyy.png new file mode 100644 index 00000000000..c640f9881c9 Binary files /dev/null and b/docs/images/jereeemyyyy.png differ diff --git a/docs/images/l-rrrr.png b/docs/images/l-rrrr.png new file mode 100644 index 00000000000..ff819094a3e Binary files /dev/null and b/docs/images/l-rrrr.png differ diff --git a/docs/images/result_for_add.png b/docs/images/result_for_add.png new file mode 100644 index 00000000000..c0e30156087 Binary files /dev/null and b/docs/images/result_for_add.png differ diff --git a/docs/images/result_for_add_reminder.png b/docs/images/result_for_add_reminder.png new file mode 100644 index 00000000000..5d4620a8e1a Binary files /dev/null and b/docs/images/result_for_add_reminder.png differ diff --git a/docs/images/result_for_delete.png b/docs/images/result_for_delete.png new file mode 100644 index 00000000000..072bdecf17a Binary files /dev/null and b/docs/images/result_for_delete.png differ diff --git a/docs/images/result_for_delete_reminder.png b/docs/images/result_for_delete_reminder.png new file mode 100644 index 00000000000..4e05749800e Binary files /dev/null and b/docs/images/result_for_delete_reminder.png differ diff --git a/docs/images/result_for_edit.png b/docs/images/result_for_edit.png new file mode 100644 index 00000000000..8ed809e3ad6 Binary files /dev/null and b/docs/images/result_for_edit.png differ diff --git a/docs/images/result_for_edit_reminder.png b/docs/images/result_for_edit_reminder.png new file mode 100644 index 00000000000..2520f2c5864 Binary files /dev/null and b/docs/images/result_for_edit_reminder.png differ diff --git a/docs/images/result_for_find_address.png b/docs/images/result_for_find_address.png new file mode 100644 index 00000000000..823dc88070f Binary files /dev/null and b/docs/images/result_for_find_address.png differ diff --git a/docs/images/result_for_find_clienttypes.png b/docs/images/result_for_find_clienttypes.png new file mode 100644 index 00000000000..d8e0ac7d35d Binary files /dev/null and b/docs/images/result_for_find_clienttypes.png differ diff --git a/docs/images/result_for_find_name.png b/docs/images/result_for_find_name.png new file mode 100644 index 00000000000..6c64005fcb8 Binary files /dev/null and b/docs/images/result_for_find_name.png differ diff --git a/docs/images/result_for_find_phone.png b/docs/images/result_for_find_phone.png new file mode 100644 index 00000000000..114139f0663 Binary files /dev/null and b/docs/images/result_for_find_phone.png differ diff --git a/docs/images/result_for_view.png b/docs/images/result_for_view.png new file mode 100644 index 00000000000..22ecdc7b182 Binary files /dev/null and b/docs/images/result_for_view.png differ diff --git a/docs/images/rubinnn.png b/docs/images/rubinnn.png new file mode 100644 index 00000000000..30a1939721a Binary files /dev/null and b/docs/images/rubinnn.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..277f971bea7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,17 +1,17 @@ --- layout: page -title: AddressBook Level-3 +title: ClientHub --- -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) -[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3) +[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/AY2425S1-CS2103T-F10-1/tp/actions) +[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://app.codecov.io/gh/AY2425S1-CS2103T-F10-1/tp) ![Ui](images/Ui.png) -**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). +**ClientHub is a desktop application for managing your client's 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. +* If you are interested in using ClientHub, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). +* If you are interested about developing ClientHub, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. **Acknowledgements** diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 678ddc8c218..3f7e53d1499 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -15,15 +15,15 @@ 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.ClientHub; import seedu.address.model.Model; import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyClientHub; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; import seedu.address.model.util.SampleDataUtil; -import seedu.address.storage.AddressBookStorage; -import seedu.address.storage.JsonAddressBookStorage; +import seedu.address.storage.ClientHubStorage; +import seedu.address.storage.JsonClientHubStorage; import seedu.address.storage.JsonUserPrefsStorage; import seedu.address.storage.Storage; import seedu.address.storage.StorageManager; @@ -48,7 +48,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing ClientHub ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -57,8 +57,8 @@ 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); + ClientHubStorage clientHubStorage = new JsonClientHubStorage(userPrefs.getClientHubFilePath()); + storage = new StorageManager(clientHubStorage, userPrefsStorage); model = initModelManager(storage, userPrefs); @@ -73,21 +73,21 @@ public void init() throws Exception { * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book. */ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { - logger.info("Using data file : " + storage.getAddressBookFilePath()); + logger.info("Using data file : " + storage.getClientHubFilePath()); - Optional addressBookOptional; - ReadOnlyAddressBook initialData; + Optional clientHubOptional; + ReadOnlyClientHub initialData; try { - addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { - logger.info("Creating a new data file " + storage.getAddressBookFilePath() - + " populated with a sample AddressBook."); + clientHubOptional = storage.readClientHub(); + if (!clientHubOptional.isPresent()) { + logger.info("Creating a new data file " + storage.getClientHubFilePath() + + " populated with a sample ClientHub."); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + initialData = clientHubOptional.orElseGet(SampleDataUtil::getSampleClientHub); } catch (DataLoadingException e) { - logger.warning("Data file at " + storage.getAddressBookFilePath() + " could not be loaded." - + " Will be starting with an empty AddressBook."); - initialData = new AddressBook(); + logger.warning("Data file at " + storage.getClientHubFilePath() + " could not be loaded." + + " Will be starting with an empty ClientHub."); + initialData = new ClientHub(); } return new ModelManager(initialData, userPrefs); @@ -170,13 +170,13 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting ClientHub " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping AddressBook ] ============================="); + logger.info("============================ [ Stopping ClientHub ] ============================="); try { storage.saveUserPrefs(model.getUserPrefs()); } catch (IOException e) { diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/address/commons/core/LogsCenter.java index 8cf8e15a0f0..4a708a8ee49 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/seedu/address/commons/core/LogsCenter.java @@ -20,7 +20,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 = "clienthub.log"; private static final Logger logger; // logger for this class private static Logger baseLogger; // to be used as the parent of all other loggers created by this class. private static Level currentLogLevel = Level.INFO; diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..408ae85426c 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -7,8 +7,9 @@ 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.ReadOnlyClientHub; import seedu.address.model.person.Person; +import seedu.address.model.reminder.Reminder; /** * API of the Logic component @@ -24,19 +25,22 @@ public interface Logic { CommandResult execute(String commandText) throws CommandException, ParseException; /** - * Returns the AddressBook. + * Returns the ClientHub. * - * @see seedu.address.model.Model#getAddressBook() + * @see seedu.address.model.Model#getClientHub() */ - ReadOnlyAddressBook getAddressBook(); + ReadOnlyClientHub getClientHub(); /** Returns an unmodifiable view of the filtered list of persons */ - ObservableList getFilteredPersonList(); + ObservableList getPersonList(); + + /** Returns an unmodifiable view of the filtered list of reminders */ + ObservableList getReminderList(); /** - * Returns the user prefs' address book file path. + * Returns the user prefs' client hub file path. */ - Path getAddressBookFilePath(); + Path getClientHubFilePath(); /** * Returns the user prefs' GUI settings. diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 5aa3b91c7d0..dd49ac5e044 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -11,11 +11,12 @@ import seedu.address.logic.commands.Command; import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.ClientHubParser; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyClientHub; import seedu.address.model.person.Person; +import seedu.address.model.reminder.Reminder; import seedu.address.storage.Storage; /** @@ -31,7 +32,7 @@ public class LogicManager implements Logic { private final Model model; private final Storage storage; - private final AddressBookParser addressBookParser; + private final ClientHubParser clientHubParser; /** * Constructs a {@code LogicManager} with the given {@code Model} and {@code Storage}. @@ -39,7 +40,7 @@ public class LogicManager implements Logic { public LogicManager(Model model, Storage storage) { this.model = model; this.storage = storage; - addressBookParser = new AddressBookParser(); + clientHubParser = new ClientHubParser(); } @Override @@ -47,11 +48,11 @@ public CommandResult execute(String commandText) throws CommandException, ParseE logger.info("----------------[USER COMMAND][" + commandText + "]"); CommandResult commandResult; - Command command = addressBookParser.parseCommand(commandText); + Command command = clientHubParser.parseCommand(commandText); commandResult = command.execute(model); try { - storage.saveAddressBook(model.getAddressBook()); + storage.saveClientHub(model.getClientHub()); } catch (AccessDeniedException e) { throw new CommandException(String.format(FILE_OPS_PERMISSION_ERROR_FORMAT, e.getMessage()), e); } catch (IOException ioe) { @@ -62,18 +63,23 @@ public CommandResult execute(String commandText) throws CommandException, ParseE } @Override - public ReadOnlyAddressBook getAddressBook() { - return model.getAddressBook(); + public ReadOnlyClientHub getClientHub() { + return model.getClientHub(); } @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); + public ObservableList getPersonList() { + return model.getDisplayPersons(); } @Override - public Path getAddressBookFilePath() { - return model.getAddressBookFilePath(); + public ObservableList getReminderList() { + return model.getDisplayReminders(); + } + + @Override + public Path getClientHubFilePath() { + return model.getClientHubFilePath(); } @Override diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java index ecd32c31b53..2758a697519 100644 --- a/src/main/java/seedu/address/logic/Messages.java +++ b/src/main/java/seedu/address/logic/Messages.java @@ -6,18 +6,38 @@ import seedu.address.logic.parser.Prefix; import seedu.address.model.person.Person; +import seedu.address.model.reminder.Reminder; /** * Container for user visible messages. */ public class Messages { - public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; + public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command! \n" + + "Please use the help command to see all available commands."; 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_DUPLICATE_FIELDS = "Multiple values specified for the following single-valued field(s): "; + public static final String MESSAGE_PERSON_NOT_FOUND = "Client not found. \n" + + "Please double check the name of the client!"; + public static final String MESSAGE_ADDRESS_NOT_FOUND = "Client not found. \n" + + "Please double check the address of the client!"; + public static final String MESSAGE_PHONE_NOT_FOUND = "Client not found. \n" + + "Please double check the phone number of the client!"; + public static final String MESSAGE_CLIENT_TYPE_NOT_FOUND = "Client not found. \n" + + "Please double check the client type(s) of the client!"; + public static final String MESSAGE_VAGUE_DELETE = "Please be more specific in the name \n" + + "or use $ to indicate the end of an EXACT name"; + public static final String MESSAGE_PERSON_LISTED_OVERVIEW_FOR_VIEW = "%1$d client found for viewing!"; + public static final String MESSAGE_NO_PERSON_FOUND_FOR_VIEW = + "No clients found please use the list command to see all clients"; + public static final String MESSAGE_INVALID_REMINDER_DISPLAYED_INDEX = "The reminder index provided is invalid"; + public static final String MESSAGE_TARGET_DELETE_HAS_REMINDER = "The client has reminders, " + + "please delete them first"; + public static final String MESSAGE_NO_CLIENT_TYPE = + "There should be at least one CLIENT_TYPE at all times"; + public static final String MESSAGE_EDIT_PERSON_HAS_REMINDER = "The client has reminders, please delete them first!"; /** * Returns an error message indicating the duplicate prefixes. @@ -31,6 +51,25 @@ public static String getErrorMessageForDuplicatePrefixes(Prefix... duplicatePref return MESSAGE_DUPLICATE_FIELDS + String.join(" ", duplicateFields); } + /** + * Returns an overview message of the number of persons listed. + * The message will use the singular form "person" if the count is 1, + * and the plural form "persons" otherwise. + * + * @param count The number of persons listed. + * @return A formatted string with the count and the correct singular or plural term. + */ + public static String getMessagePersonsListedOverview(int count) { + if (count == 0) { + return "0 client listed!, please use the list command to see all clients!"; + } + if (count == 1) { + return "1 client listed!"; + } + return String.format("%1$d clients listed!", count); + } + + /** * Formats the {@code person} for display to the user. */ @@ -43,9 +82,29 @@ public static String format(Person person) { .append(person.getEmail()) .append("; Address: ") .append(person.getAddress()) - .append("; Tags: "); - person.getTags().forEach(builder::append); + .append("; Client Types: "); + person.getClientTypes().forEach(builder::append); + builder.append("; Description: ") + .append(person.getDescription()); return builder.toString(); } + /** + * Formats a {@code Reminder} object into a specific string representation. + * + *

The formatted string includes the person associated with the reminder, + * the date and time of the reminder, and a description of the reminder, separated by semicolons. + * + * @param reminder The {@code Reminder} object to format. + * @return A formatted string containing the person's name, date and time, and description of the reminder. + */ + public static String format(Reminder reminder) { + final StringBuilder builder = new StringBuilder(); + builder.append(reminder.getPersonName()) + .append("; Date and Time: ") + .append(reminder.getFormattedDateTime()) + .append("; Description: ") + .append(reminder.getDescription()); + return builder.toString(); + } } diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index 5d7185a9680..f58bf158f6f 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -2,10 +2,11 @@ import static java.util.Objects.requireNonNull; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CLIENT_TYPE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import seedu.address.commons.util.ToStringBuilder; import seedu.address.logic.Messages; @@ -20,23 +21,31 @@ 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 SHORT_COMMAND_WORD = "a"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a client to ClientHub.\n" + "Parameters: " + PREFIX_NAME + "NAME " + PREFIX_PHONE + "PHONE " + PREFIX_EMAIL + "EMAIL " + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" + + PREFIX_DESCRIPTION + "DESCRIPTION " + + PREFIX_CLIENT_TYPE + "CLIENT_TYPE\n" + "Example: " + COMMAND_WORD + " " + PREFIX_NAME + "John Doe " + PREFIX_PHONE + "98765432 " + PREFIX_EMAIL + "johnd@example.com " + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; + + PREFIX_CLIENT_TYPE + "Plan A " + + PREFIX_CLIENT_TYPE + "Plan B " + + PREFIX_DESCRIPTION + "Likes to eat a lot \n" + + "Additional Info:\n" + + "- Can add multiple c/ to add multiple CLIENT_TYPE\n"; - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; + public static final String MESSAGE_SUCCESS = "New client added: %1$s"; + public static final String MESSAGE_DUPLICATE_PERSON = "This name already exists in Client Hub. If the " + + "newly added client has the same name, please use parenthesis to add more information after the name " + + "to differentiate the clients."; private final Person toAdd; diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index 9c86b1fa6e4..17ed429c3b0 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -2,22 +2,22 @@ import static java.util.Objects.requireNonNull; -import seedu.address.model.AddressBook; +import seedu.address.model.ClientHub; import seedu.address.model.Model; /** - * Clears the address book. + * Clears the client hub. */ public class ClearCommand extends Command { public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; + public static final String MESSAGE_SUCCESS = "Client Hub has been cleared!"; @Override public CommandResult execute(Model model) { requireNonNull(model); - model.setAddressBook(new AddressBook()); + model.setClientHub(new ClientHub()); 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 249b6072d0d..090c0c1472a 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/address/logic/commands/CommandResult.java @@ -13,19 +13,24 @@ public class CommandResult { private final String feedbackToUser; + /** View information should be shown to the user. */ + private final boolean showView; + /** Help information should be shown to the user. */ private final boolean showHelp; /** The application should exit. */ private final boolean exit; + /** * Constructs a {@code CommandResult} with the specified fields. */ - public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { + public CommandResult(String feedbackToUser, boolean showHelp, boolean showView, boolean exit) { this.feedbackToUser = requireNonNull(feedbackToUser); this.showHelp = showHelp; this.exit = exit; + this.showView = showView; } /** @@ -33,7 +38,7 @@ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) { * and other fields set to their default value. */ public CommandResult(String feedbackToUser) { - this(feedbackToUser, false, false); + this(feedbackToUser, false, false, false); } public String getFeedbackToUser() { @@ -48,6 +53,10 @@ public boolean isExit() { return exit; } + public boolean isShowView() { + return showView; + } + @Override public boolean equals(Object other) { if (other == this) { @@ -62,12 +71,13 @@ public boolean equals(Object other) { CommandResult otherCommandResult = (CommandResult) other; return feedbackToUser.equals(otherCommandResult.feedbackToUser) && showHelp == otherCommandResult.showHelp + && showView == otherCommandResult.showView && exit == otherCommandResult.exit; } @Override public int hashCode() { - return Objects.hash(feedbackToUser, showHelp, exit); + return Objects.hash(feedbackToUser, showHelp, showView, exit); } @Override @@ -75,6 +85,7 @@ public String toString() { return new ToStringBuilder(this) .add("feedbackToUser", feedbackToUser) .add("showHelp", showHelp) + .add("showView", showView) .add("exit", exit) .toString(); } diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index 1135ac19b74..a92618150f6 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -1,14 +1,13 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; -import java.util.List; - -import seedu.address.commons.core.index.Index; import seedu.address.commons.util.ToStringBuilder; import seedu.address.logic.Messages; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; +import seedu.address.model.person.NameContainsKeywordsDeletePredicate; import seedu.address.model.person.Person; /** @@ -17,31 +16,47 @@ public class DeleteCommand extends Command { public static final String COMMAND_WORD = "delete"; + public static final String SHORT_COMMAND_WORD = "d"; public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; + + ": Deletes the client identified by their name in the displayed client list.\n" + + "Parameters: NAME (String & must be non-empty)\n" + + "Example: " + COMMAND_WORD + " John Doe " + + " or " + " " + + SHORT_COMMAND_WORD + " John Doe \n" + + "Additional Info: \n" + + "- To delete a client with a common name, please provide fullname or\n" + + "use $ to indicate the end of the name eg Jon snow$"; - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; + public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Client: %1$s"; - private final Index targetIndex; + private final NameContainsKeywordsDeletePredicate predicate; - public DeleteCommand(Index targetIndex) { - this.targetIndex = targetIndex; + public DeleteCommand(NameContainsKeywordsDeletePredicate predicate) { + this.predicate = predicate; } @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } + model.updateFilteredPersonList(predicate); + if (model.getDisplayPersons().isEmpty()) { + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + throw new CommandException(Messages.MESSAGE_PERSON_NOT_FOUND); - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); + } + if (model.getDisplayPersons().size() > 1) { + model.updateFilteredPersonList(predicate); + throw new CommandException(Messages.MESSAGE_VAGUE_DELETE); + } + Person personToDelete = model.getDisplayPersons().get(0); + if (!personToDelete.getReminders().isEmpty()) { + model.updateFilteredPersonList(predicate); + throw new CommandException(Messages.MESSAGE_TARGET_DELETE_HAS_REMINDER); + } model.deletePerson(personToDelete); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(personToDelete))); } @@ -57,13 +72,13 @@ public boolean equals(Object other) { } DeleteCommand otherDeleteCommand = (DeleteCommand) other; - return targetIndex.equals(otherDeleteCommand.targetIndex); + return predicate.equals(otherDeleteCommand.predicate); } @Override public String toString() { return new ToStringBuilder(this) - .add("targetIndex", targetIndex) + .add("predicate", predicate) .toString(); } } diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index 4b581c7331e..105f53086b8 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -1,11 +1,13 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.MESSAGE_EDIT_PERSON_HAS_REMINDER; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CLIENT_TYPE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; 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; @@ -21,12 +23,14 @@ import seedu.address.logic.Messages; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; +import seedu.address.model.clienttype.ClientType; import seedu.address.model.person.Address; +import seedu.address.model.person.Description; 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.reminder.Reminder; /** * Edits the details of an existing person in the address book. @@ -35,22 +39,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 client identified " + + "by the index number used in the displayed client 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_EMAIL + "EMAIL] " + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" + + "[" + PREFIX_CLIENT_TYPE + "CLIENT_TYPE] " + + "[" + PREFIX_DESCRIPTION + "DESCRIPTION]\n" + "Example: " + COMMAND_WORD + " 1 " + PREFIX_PHONE + "91234567 " + PREFIX_EMAIL + "johndoe@example.com"; - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; + public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Client: %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 name already exists in the client hub. If the " + + "newly added client has the same name, Please add (ADDITIONAL INFORMATION) to differentiate the names."; private final Index index; private final EditPersonDescriptor editPersonDescriptor; @@ -70,7 +76,7 @@ public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); + List lastShownList = model.getDisplayPersons(); if (index.getZeroBased() >= lastShownList.size()) { throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); @@ -79,6 +85,10 @@ public CommandResult execute(Model model) throws CommandException { Person personToEdit = lastShownList.get(index.getZeroBased()); Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); + if (!personToEdit.getReminders().isEmpty() && editPersonDescriptor.isNameEdited()) { + throw new CommandException(MESSAGE_EDIT_PERSON_HAS_REMINDER); + } + if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { throw new CommandException(MESSAGE_DUPLICATE_PERSON); } @@ -99,9 +109,16 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript 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()); + Set updatedClientTypes = + editPersonDescriptor.getClientTypes().orElse(personToEdit.getClientTypes()); + Description updatedDescription = editPersonDescriptor.getDescription() + .orElse(personToEdit.getDescription()); + + // Edit Command doesn't allow editing of reminders + // Set updatedReminders = editPersonDescriptor.getReminders().orElse(personToEdit.getReminders()); - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, + updatedClientTypes, updatedDescription, personToEdit.getReminders()); } @Override @@ -137,27 +154,34 @@ public static class EditPersonDescriptor { private Phone phone; private Email email; private Address address; - private Set tags; + private Set clientTypes; + private Description description; + private Set reminders; public EditPersonDescriptor() {} /** * Copy constructor. - * A defensive copy of {@code tags} is used internally. + * A defensive copy of {@code clientTypes} is used internally. */ public EditPersonDescriptor(EditPersonDescriptor toCopy) { setName(toCopy.name); setPhone(toCopy.phone); setEmail(toCopy.email); setAddress(toCopy.address); - setTags(toCopy.tags); + setClientTypes(toCopy.clientTypes); + 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, address, clientTypes, description); + } + + public boolean isNameEdited() { + return this.name != null; } public void setName(Name name) { @@ -188,25 +212,38 @@ public void setAddress(Address address) { this.address = address; } + public Optional

getAddress() { return Optional.ofNullable(address); } /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. + * Sets {@code clientTypes} to this object's {@code clientTypes}. + * A defensive copy of {@code clientTypes} is used internally. */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; + public void setClientTypes(Set clientTypes) { + this.clientTypes = (clientTypes != null) ? new HashSet<>(clientTypes) : null; } /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * Returns an unmodifiable client types set, which throws {@code UnsupportedOperationException} * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. + * Returns {@code Optional#empty()} if {@code clientTypes} is null. */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + public Optional> getClientTypes() { + return (clientTypes != null) ? Optional.of(Collections.unmodifiableSet(clientTypes)) : Optional.empty(); + } + + public void setDescription(Description description) { + this.description = description; + } + + public Optional getDescription() { + return Optional.ofNullable(description); + } + + public Optional> getReminders() { + return (reminders != null) ? Optional.of(Collections.unmodifiableSet(reminders)) : Optional.empty(); } @Override @@ -225,7 +262,9 @@ public boolean equals(Object other) { && Objects.equals(phone, otherEditPersonDescriptor.phone) && Objects.equals(email, otherEditPersonDescriptor.email) && Objects.equals(address, otherEditPersonDescriptor.address) - && Objects.equals(tags, otherEditPersonDescriptor.tags); + && Objects.equals(clientTypes, otherEditPersonDescriptor.clientTypes) + && Objects.equals(description, otherEditPersonDescriptor.description) + && Objects.equals(reminders, otherEditPersonDescriptor.reminders); } @Override @@ -235,7 +274,9 @@ public String toString() { .add("phone", phone) .add("email", email) .add("address", address) - .add("tags", tags) + .add("clientTypes", clientTypes) + .add("description", description) + .add("reminders", reminders) .toString(); } } diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index 3dd85a8ba90..57a24b5cf68 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -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 Client Hub as requested ..."; @Override public CommandResult execute(Model model) { - return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true); + return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, false, true); } } diff --git a/src/main/java/seedu/address/logic/commands/FindAddressCommand.java b/src/main/java/seedu/address/logic/commands/FindAddressCommand.java new file mode 100644 index 00000000000..037609ab1d9 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FindAddressCommand.java @@ -0,0 +1,76 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.AddressContainsKeywordsPredicate; + +/** + * Finds and lists all persons in ClientHub whose name contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class FindAddressCommand extends Command { + + public static final String COMMAND_WORD = "fa"; + + public static final String MESSAGE_USAGE = FindCommand.COMMAND_WORD + " " + PREFIX_ADDRESS + + " or " + COMMAND_WORD + + ": Finds all clients whose address contain any of " + + "the specified ADDRESS and displays them as a list with index numbers.\n" + + "Parameters: ADDRESS\n" + + "Example:\n" + + "- " + COMMAND_WORD + " tampines\n" + + "- " + FindCommand.COMMAND_WORD + " " + PREFIX_ADDRESS + "tampines\n" + + "Additional Info: \n" + + "- ADDRESS is case-insensitive.\n" + + "- ADDRESS should not be empty."; + + private final AddressContainsKeywordsPredicate predicate; + + public FindAddressCommand(AddressContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredPersonList(predicate); + + // Check if there is anyone in the filtered list + if (model.getDisplayPersons().isEmpty()) { + + // If noone found, show all persons (no change) + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + throw new CommandException(Messages.MESSAGE_ADDRESS_NOT_FOUND); + } + return new CommandResult( + Messages.getMessagePersonsListedOverview(model.getDisplayPersons().size())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FindAddressCommand)) { + return false; + } + + FindAddressCommand otherFindAddressCommand = (FindAddressCommand) other; + return predicate.equals(otherFindAddressCommand.predicate); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("predicate", predicate) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/FindClientTypeCommand.java b/src/main/java/seedu/address/logic/commands/FindClientTypeCommand.java new file mode 100644 index 00000000000..3e1eb3e0449 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FindClientTypeCommand.java @@ -0,0 +1,80 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CLIENT_TYPE; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.ClientTypeContainsKeywordsPredicate; + +/** + * Finds and lists all persons in address book whose client_type contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class FindClientTypeCommand extends Command { + + public static final String COMMAND_WORD = "fc"; + + public static final String MESSAGE_USAGE = FindCommand.COMMAND_WORD + " " + PREFIX_CLIENT_TYPE + + " or " + COMMAND_WORD + + ": Finds all clients whose names contain any of " + + "the specified CLIENT_TYPE and displays them as a list with index numbers.\n" + + "Parameters: CLIENT_TYPE [MORE_CLIENT_TYPES]...\n" + + "Examples: \n" + + COMMAND_WORD + " Investment Plan\n" + + COMMAND_WORD + " Investment Plan Healthcare\n" + + FindCommand.COMMAND_WORD + " " + PREFIX_CLIENT_TYPE + "Investment Plan\n" + + FindCommand.COMMAND_WORD + " " + PREFIX_CLIENT_TYPE + "Investment Plan Healthcare\n" + + "Additional Info: \n" + + "- CLIENT_TYPE is case-insensitive.\n" + + "- CLIENT_TYPE should not be empty.\n" + + "- Can specify multiple CLIENT_TYPE to have a more specific find."; + + + private final ClientTypeContainsKeywordsPredicate predicate; + + public FindClientTypeCommand(ClientTypeContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredPersonList(predicate); + + // Check if there is anyone in the filtered list + if (model.getDisplayPersons().isEmpty()) { + // If noone found, show all persons (no change) + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + throw new CommandException(Messages.MESSAGE_CLIENT_TYPE_NOT_FOUND); + } + return new CommandResult( + Messages.getMessagePersonsListedOverview(model.getDisplayPersons().size())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FindClientTypeCommand)) { + return false; + } + + FindClientTypeCommand otherFindCommand = (FindClientTypeCommand) other; + return predicate.equals(otherFindCommand.predicate); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("predicate", predicate) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index 72b9eddd3a7..b1724469193 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -8,17 +8,24 @@ import seedu.address.model.person.NameContainsKeywordsPredicate; /** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case insensitive. + * Finds and lists all persons in ClientHub whose name contains any of the argument keywords. + * Keyword matching is case-insensitive. */ public class FindCommand extends Command { public static final String COMMAND_WORD = "find"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " - + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds clients whose name, phone number, " + + "address or client type contain any of " + + "all specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + + "Parameters: n/NAME or p/PHONE_NUMBER, or a/ADDRESS or c/CLIENT_TYPE\n" + + "Examples:\n" + + "- " + COMMAND_WORD + " alice wong\n" + + "- " + COMMAND_WORD + " p/91234567\n" + + "- " + COMMAND_WORD + " a/123, Jurong West Ave 6\n" + + "- " + COMMAND_WORD + " c/Investment Plan 1\n" + + "Additional Info:\n" + + "- The command can only take in one prefix at any point of time. (find n/NAME a/Address is invalid)\n"; private final NameContainsKeywordsPredicate predicate; @@ -31,7 +38,7 @@ public CommandResult execute(Model model) { requireNonNull(model); model.updateFilteredPersonList(predicate); return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + Messages.getMessagePersonsListedOverview(model.getDisplayPersons().size())); } @Override diff --git a/src/main/java/seedu/address/logic/commands/FindNameCommand.java b/src/main/java/seedu/address/logic/commands/FindNameCommand.java new file mode 100644 index 00000000000..bbfeea81955 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FindNameCommand.java @@ -0,0 +1,78 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.NameContainsKeywordsPredicate; + +/** + * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Keyword matching is case-insensitive. + */ +public class FindNameCommand extends Command { + + public static final String COMMAND_WORD = "fn"; + + public static final String MESSAGE_USAGE = FindCommand.COMMAND_WORD + " " + PREFIX_NAME + + " or " + COMMAND_WORD + + ": Finds all clients whose names contain all of " + + "the prefix of the specified NAME and displays them as a list with index numbers.\n" + + "Parameters: NAME (String & must be non-empty)\n" + + "Example:\n" + + "- " + COMMAND_WORD + " Alice\n" + + "- " + FindCommand.COMMAND_WORD + " " + PREFIX_NAME + "Alice\n" + + "Additional Info: \n" + + "- NAME is case-insensitive.\n" + + "- It should contain letters, spaces, parenthesis or slashes only.\n" + + "- They cannot be empty or have only spaces."; + + private final NameContainsKeywordsPredicate predicate; + + public FindNameCommand(NameContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredPersonList(predicate); + + // Check if there is anyone in the filtered list + if (model.getDisplayPersons().isEmpty()) { + + // If noone found, show all persons (no change) + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + throw new CommandException(Messages.MESSAGE_PERSON_NOT_FOUND); + } + + return new CommandResult( + Messages.getMessagePersonsListedOverview(model.getDisplayPersons().size())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FindNameCommand)) { + return false; + } + + FindNameCommand otherFindNameCommand = (FindNameCommand) other; + return predicate.equals(otherFindNameCommand.predicate); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("predicate", predicate) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/FindPhoneCommand.java b/src/main/java/seedu/address/logic/commands/FindPhoneCommand.java new file mode 100644 index 00000000000..4bdbcf8c05a --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FindPhoneCommand.java @@ -0,0 +1,76 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.PhoneBeginsWithKeywordPredicate; + +/** + * Finds and lists all persons in address book whose phone number begins with the argument keywords. + * Keyword matching has to be all numbers. + */ +public class FindPhoneCommand extends Command { + + public static final String COMMAND_WORD = "fp"; + + public static final String MESSAGE_USAGE = FindCommand.COMMAND_WORD + " " + PREFIX_PHONE + + " or " + COMMAND_WORD + + ": Finds all clients whose phone number begins with " + + "the specified PHONE_NUMBER and displays them as a list with index numbers.\n" + + "Parameters: PHONE_NUMBER (Contains only 8 digits)\n" + + "Example:\n" + + "- " + COMMAND_WORD + " 91234567\n" + + "- " + FindCommand.COMMAND_WORD + " " + PREFIX_PHONE + "91234567\n" + + "Additional Info: \n" + + "- PHONE_NUMBER is a sequence of integers with no spacing."; + + + private final PhoneBeginsWithKeywordPredicate predicate; + + public FindPhoneCommand(PhoneBeginsWithKeywordPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateFilteredPersonList(predicate); + + // Check if there is anyone in the filtered list + if (model.getDisplayPersons().isEmpty()) { + + // If noone found, show all persons (no change) + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + throw new CommandException(Messages.MESSAGE_PHONE_NOT_FOUND); + } + return new CommandResult( + Messages.getMessagePersonsListedOverview(model.getDisplayPersons().size())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FindPhoneCommand)) { + return false; + } + + FindPhoneCommand otherFindCommand = (FindPhoneCommand) other; + return predicate.equals(otherFindCommand.predicate); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("predicate", predicate) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index bf824f91bd0..07d26e2a23c 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -16,6 +16,6 @@ public class HelpCommand extends Command { @Override public CommandResult execute(Model model) { - return new CommandResult(SHOWING_HELP_MESSAGE, true, false); + return new CommandResult(SHOWING_HELP_MESSAGE, true, false, false); } } diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 84be6ad2596..18a34475bda 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -1,7 +1,6 @@ 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; @@ -11,14 +10,15 @@ public class ListCommand extends Command { public static final String COMMAND_WORD = "list"; + public static final String SHORT_COMMAND_WORD = "li"; - public static final String MESSAGE_SUCCESS = "Listed all persons"; + public static final String MESSAGE_SUCCESS = "Listed all clients."; @Override public CommandResult execute(Model model) { requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateUnfilteredList(); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/commands/SortCommand.java b/src/main/java/seedu/address/logic/commands/SortCommand.java new file mode 100644 index 00000000000..18731c61bdc --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SortCommand.java @@ -0,0 +1,65 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.Comparator; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.NameComparator; +import seedu.address.model.person.Person; + +/** + * Sorts the list of contacts in Client Hub. + */ +public class SortCommand extends Command { + + public static final String COMMAND_WORD = "sort"; + public static final String SHORT_COMMAND_WORD = "s"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + " CRITERIA" + + ": Sorts the list according to criteria given.\n" + + "Usage: sort"; + + public static final String MESSAGE_SUCCESS = "List sorted successfully!"; + + private final Comparator comparator; + + /** + * Creates an SortCommand to sort the current list of contacts. + */ + public SortCommand() { + this.comparator = new NameComparator(); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + model.updateSortedPersonList(comparator); + return new CommandResult(MESSAGE_SUCCESS); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof SortCommand)) { + return false; + } + + SortCommand otherSortCommand = (SortCommand) other; + return comparator.equals(otherSortCommand.comparator); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("comparator", comparator) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ViewCommand.java b/src/main/java/seedu/address/logic/commands/ViewCommand.java new file mode 100644 index 00000000000..75655358a8b --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ViewCommand.java @@ -0,0 +1,91 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.NameContainsKeywordsPredicate; + + +/** + * Finds and creates a view popup of the specified client whose name contains any of the argument keywords. + * Keyword matching is case in-sensitive. + */ +public class ViewCommand extends Command { + + public static final String COMMAND_WORD = "view"; + public static final String SHORT_COMMAND_WORD = "v"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Creates a view of the specified client whose name" + + "contain any of " + + "the specified keywords (case-insensitive) and display them as a popup modal.\n" + + "Parameters: NAME\n" + + "Example: " + COMMAND_WORD + " " + "alice"; + + public static final String MORE_THAN_ONE_PERSON_VIEW_MESSAGE = + "\nMultiple clients found. Please specify the name of your client further."; + + public static final String NO_PERSON_FOUND_VIEW_MESSAGE = + "Client not found for viewing. Please double check the name of your client!"; + + public static final String SHOWING_VIEW_MESSAGE = "Opened view window."; + + private final NameContainsKeywordsPredicate predicate; + + public ViewCommand(NameContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + // In the list, it will show the person that is viewed + model.updateFilteredPersonList(predicate); + + // Check if there is anyone in the filtered list + if (model.getDisplayPersons().isEmpty()) { + + // If noone found, show all persons (no change) + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + throw new CommandException(NO_PERSON_FOUND_VIEW_MESSAGE); + } + + // Check if there are duplicates + if (model.getDisplayPersons().size() > 1) { + throw new CommandException( + String.format(Messages.MESSAGE_PERSON_LISTED_OVERVIEW_FOR_VIEW, + model.getDisplayPersonsListSize()) + + MORE_THAN_ONE_PERSON_VIEW_MESSAGE + ); + } + return new CommandResult( + String.format(Messages.MESSAGE_PERSON_LISTED_OVERVIEW_FOR_VIEW, model.getDisplayPersons().size()), + false, true, false); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ViewCommand)) { + return false; + } + + ViewCommand otherViewCommand = (ViewCommand) other; + return predicate.equals(otherViewCommand.predicate); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("predicate", predicate) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/reminder/AddReminderCommand.java b/src/main/java/seedu/address/logic/commands/reminder/AddReminderCommand.java new file mode 100644 index 00000000000..99c19b45158 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/reminder/AddReminderCommand.java @@ -0,0 +1,126 @@ +package seedu.address.logic.commands.reminder; +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_TIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; + +import java.util.List; + +import seedu.address.logic.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.Person; +import seedu.address.model.reminder.Reminder; + +/** + * Adds a reminder to the address book. + */ +public class AddReminderCommand extends Command { + public static final String COMMAND_WORD = "radd"; // reminder add + public static final String COMMAND_WORD_SHORT = "ra"; // reminder add + + public static final String MESSAGE_USAGE = COMMAND_WORD + " or " + COMMAND_WORD_SHORT + + ": Adds a reminder to Client hub.\n" + + "Parameters: " + + PREFIX_NAME + "NAME, " + + PREFIX_DATE_TIME + "DATE_AND_TIME, " + + PREFIX_DESCRIPTION + "DESCRIPTION\n" + + "Examples:\n" + + "- " + COMMAND_WORD + " " + PREFIX_NAME + "John " + PREFIX_DATE_TIME + "2021-12-31 23:59 " + + PREFIX_DESCRIPTION + "New Year's Eve\n" + + "- " + COMMAND_WORD_SHORT + " " + PREFIX_NAME + "John " + PREFIX_DATE_TIME + "2021-12-31 23:59 " + + PREFIX_DESCRIPTION + "New Year's Eve\n"; + + + public static final String MESSAGE_SUCCESS = "New reminder added: %1$s"; + public static final String MESSAGE_NONEXISTENT_PERSON = "This client doesn't exist in the Client Hub."; + public static final String MESSAGE_MORE_THAN_ONE_PERSON = "There is more than one client with this name in " + + "client hub. Please use a more specific name instead."; + + private final Reminder toAdd; + + /** + * Constructs an {@code AddReminderCommand} with the specified {@code Reminder}. + * + *

This command initializes an instance that will add the given reminder. + * The reminder must not be null. + * + * @param reminder The {@code Reminder} to be added. + * @throws NullPointerException if {@code reminder} is null. + */ + public AddReminderCommand(Reminder reminder) { + requireNonNull(reminder); + toAdd = reminder; + } + + /** + * Executes the command to add a reminder to the model. + * + * @param model The {@code Model} which the command should operate on. + * @return A {@code CommandResult} indicating the outcome of the command execution. + * @throws CommandException If there is an issue during command execution. + */ + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + // Parse input using the NameContainsKeywordsPredicate + String[] nameKeywords = toAdd.getPersonName().split("\\s+"); + NameContainsKeywordsPredicate predicate = new NameContainsKeywordsPredicate(List.of(nameKeywords)); + List matchingPersons = model.getClientHub().getPersonList().filtered(predicate); + + // Check if there is exactly one match + if (matchingPersons.size() == 1) { + Person person = matchingPersons.get(0); + + // Creates a new reminder with the full name of the person + Reminder reminderWithFullName = toAdd.getReminderWithFullName(person.getName().fullName); + person.addReminder(reminderWithFullName); + model.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_PERSONS); + + return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(reminderWithFullName))); + } else if (matchingPersons.isEmpty()) { + throw new CommandException(MESSAGE_NONEXISTENT_PERSON); + } else { + model.updateFilteredPersonList(predicate); + throw new CommandException(MESSAGE_MORE_THAN_ONE_PERSON); + } + } + + /** + * Checks if this {@code AddReminderCommand} is equal to another object. + * + * @param other The other object to compare to. + * @return {@code true} if the other object is an {@code AddReminderCommand}, otherwise {@code false}. + */ + @Override + public boolean equals(Object other) { + // Check if same object + if (other == this) { + return true; + } + + // Check if instance of AddReminderCommand and compare reminders + if (other instanceof AddReminderCommand) { + AddReminderCommand otherCommand = (AddReminderCommand) other; + return toAdd.equals(otherCommand.toAdd); + } + + // If neither of the above, return false + return false; + } + + /** + * Returns a string representation of the {@code AddReminderCommand}. + * + * @return A string representation of the command. + */ + @Override + public String toString() { + return "AddReminderCommand {" + "toAdd=" + toAdd + '}'; + } +} diff --git a/src/main/java/seedu/address/logic/commands/reminder/DeleteReminderCommand.java b/src/main/java/seedu/address/logic/commands/reminder/DeleteReminderCommand.java new file mode 100644 index 00000000000..10b1828accd --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/reminder/DeleteReminderCommand.java @@ -0,0 +1,109 @@ +package seedu.address.logic.commands.reminder; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.NameContainsKeywordsDeletePredicate; +import seedu.address.model.person.Person; +import seedu.address.model.reminder.Reminder; + +/** + * Deletes a reminder identified using its displayed index from the reminder list. + */ +public class DeleteReminderCommand extends Command { + + public static final String COMMAND_WORD = "rdelete"; + public static final String COMMAND_WORD_SHORT = "rd"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + " or " + COMMAND_WORD_SHORT + + ": Deletes the reminder " + + "identified by the index number used in the displayed reminder list.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example:\n" + + "- " + COMMAND_WORD + " 1\n" + + "- " + COMMAND_WORD_SHORT + " 1"; + + public static final String MESSAGE_DELETE_REMINDER_SUCCESS = "Deleted Reminder: %1$s"; + + public static final String MESSAGE_INVALID_REMINDER_DISPLAYED_INDEX = "The reminder index provided is invalid"; + + private final Index targetIndex; + + /** + * Constructs a {@code DeleteReminderCommand} with the specified target index. + * + * @param targetIndex The index of the reminder to be deleted. + */ + public DeleteReminderCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + /** + * Executes the command to delete the reminder from the model. + * + * @param model The {@code Model} which the command should operate on. + * @return A {@code CommandResult} indicating the result of the command execution. + * @throws CommandException If the specified index is invalid (out of bounds). + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + List lastShownList = model.getDisplayReminders(); + + if (targetIndex.getZeroBased() >= lastShownList.size() || targetIndex.getZeroBased() < 0) { + throw new CommandException(MESSAGE_INVALID_REMINDER_DISPLAYED_INDEX); + } + + Reminder reminderToDelete = lastShownList.get(targetIndex.getZeroBased()); + model.deleteReminder(reminderToDelete); + + // Parse input using the NameContainsKeywordsPredicate + String fullName = reminderToDelete.getPersonName() + "$"; + String[] nameKeywords = fullName.split("\\s+"); + NameContainsKeywordsDeletePredicate predicate = new NameContainsKeywordsDeletePredicate( + List.of(nameKeywords)); + ObservableList persons = model.getClientHub().getPersonList(); + List matchingPersons = persons.filtered(predicate); + + // Check if there is exactly one match + if (matchingPersons.size() == 1) { + Person person = matchingPersons.get(0); + person.deleteReminder(reminderToDelete); + return new CommandResult(String.format(MESSAGE_DELETE_REMINDER_SUCCESS, Messages.format(reminderToDelete))); + } else { + throw new CommandException("More than one person with the specified name found. Please be more specific."); + } + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + // instanceof handles nulls + if (!(other instanceof DeleteReminderCommand)) { + return false; + } + + DeleteReminderCommand otherDeleteCommand = (DeleteReminderCommand) other; + return targetIndex.equals(otherDeleteCommand.targetIndex); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIndex", targetIndex) + .toString(); + } +} + diff --git a/src/main/java/seedu/address/logic/commands/reminder/EditReminderCommand.java b/src/main/java/seedu/address/logic/commands/reminder/EditReminderCommand.java new file mode 100644 index 00000000000..9e30124e6f7 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/reminder/EditReminderCommand.java @@ -0,0 +1,192 @@ +package seedu.address.logic.commands.reminder; +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_TIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import javafx.collections.ObservableList; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.NameContainsKeywordsDeletePredicate; +import seedu.address.model.person.Person; +import seedu.address.model.reminder.Reminder; +import seedu.address.model.reminder.ReminderDescription; + +/** + * Edits a reminder in the address book. + */ +public class EditReminderCommand extends Command { + public static final String COMMAND_WORD = "redit"; // reminder edit + public static final String COMMAND_WORD_SHORT = "re"; // reminder edit + + public static final String MESSAGE_USAGE = COMMAND_WORD + " or " + COMMAND_WORD_SHORT + + ": Edits a reminder in Client Hub identified " + + "by the index number displayed in the reminder list.\n" + + "Parameters: INDEX " + + "[" + PREFIX_DATE_TIME + "DATE_TIME] " + + "[" + PREFIX_DESCRIPTION + "DESCRIPTION]\n" + + "Examples:\n" + + COMMAND_WORD + " 1 " + PREFIX_DATE_TIME + "2022-01-01 00:00 " + + PREFIX_DESCRIPTION + "New Year's\n" + + COMMAND_WORD_SHORT + " 1 " + PREFIX_DATE_TIME + "2022-01-01 00:00 " + + PREFIX_DESCRIPTION + "New Year's\n" + + "Additional Info:\n" + + "- " + "INDEX must be a positive integer.\n" + + "- " + "Existing values will be overwritten by the input values."; + + public static final String MESSAGE_EDIT_REMINDER_SUCCESS = "Edited reminder %1$s"; + public static final String MESSAGE_REMINDER_NOT_EDITED = "At least one field must be edited"; + + private final Index index; + private final EditReminderFields editReminderFields; + + /** + * @param index of the reminder in the reminder list to edit + * @param editReminderFields details of the edited reminder + */ + public EditReminderCommand(Index index, EditReminderFields editReminderFields) { + requireNonNull(index); + requireNonNull(editReminderFields); + + this.index = index; + this.editReminderFields = new EditReminderFields(editReminderFields); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getDisplayReminders(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_REMINDER_DISPLAYED_INDEX); + } + + Reminder reminderToEdit = lastShownList.get(index.getZeroBased()); + Reminder editedReminder = createEditedReminder(reminderToEdit, editReminderFields); + + if (reminderToEdit.isSameReminder(editedReminder) + || !editReminderFields.isAnyFieldEdited()) { + throw new CommandException(MESSAGE_REMINDER_NOT_EDITED); + } + + // Parse input using the NameContainsKeywordsPredicate + String fullName = reminderToEdit.getPersonName() + "$"; + String[] nameKeywords = fullName.split("\\s+"); + NameContainsKeywordsDeletePredicate predicate = new NameContainsKeywordsDeletePredicate( + List.of(nameKeywords)); + ObservableList persons = model.getClientHub().getPersonList(); + List matchingPersons = persons.filtered(predicate); + + // Check if there is exactly one match + if (matchingPersons.size() == 1) { + Person person = matchingPersons.get(0); + person.deleteReminder(reminderToEdit); + person.addReminder(editedReminder); + model.setReminder(reminderToEdit, editedReminder); + model.updateFilteredReminderList(); + return new CommandResult(String.format(MESSAGE_EDIT_REMINDER_SUCCESS, Messages.format(editedReminder))); + } else { + throw new CommandException("More than one person with the specified name found. Please be more specific."); + } + } + + /** + * Creates and returns an edited a {@code Reminder} with the details of {@code reminderToEdit} + * edited with the details of {@code editReminderFields}. + */ + public static Reminder createEditedReminder(Reminder reminderToEdit, EditReminderFields editReminderFields) { + assert reminderToEdit != null; + + // EditReminder does not allow editing of personName + String personName = reminderToEdit.getPersonName(); + LocalDateTime updatedDateTime = editReminderFields.getDateTime().orElse(reminderToEdit.getDateTime()); + ReminderDescription updatedDescription = editReminderFields.getDescription() + .orElse(reminderToEdit.getDescription()); + + return new Reminder(personName, updatedDateTime, updatedDescription); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || other instanceof EditReminderCommand; // instanceof handles nulls + } + + @Override + public String toString() { + return "EditReminderCommand"; + } + + /** + * Stores the details to edit the reminder with. Each non-empty field value will replace the + * corresponding field value of the reminder. + */ + public static class EditReminderFields { + private LocalDateTime dateTime; + private ReminderDescription description; + + /** + * Default constructor for {@code EditReminderFields}. + * Initializes an instance with no specified fields. + */ + public EditReminderFields() {} + + /** + * Copy constructor for {@code EditReminderFields}. + * Creates a new instance by copying the fields from the specified {@code EditReminderFields} instance. + * + * @param toCopy The {@code EditReminderFields} instance to copy from. Must not be {@code null}. + */ + public EditReminderFields(EditReminderFields toCopy) { + setDateTime(toCopy.dateTime); + setDescription(toCopy.description); + } + + /** + * @return true if any field is edited + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(dateTime, description); + } + + public void setDateTime(LocalDateTime dateTime) { + this.dateTime = dateTime; + } + + public Optional getDateTime() { + return Optional.ofNullable(dateTime); + } + public void setDescription(ReminderDescription description) { + this.description = description; + } + + public Optional getDescription() { + return Optional.ofNullable(description); + } + + @Override + public boolean equals(Object other) { + if (other == this || other instanceof EditReminderFields) { + return true; + } + + EditReminderFields otherEditReminderFields = (EditReminderFields) other; + return Objects.equals(dateTime, otherEditReminderFields.dateTime) + && Objects.equals(description, otherEditReminderFields.description); + } + + @Override + public String toString() { + return "EditReminderFields"; + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 4ff1a97ed77..f2712f17288 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -2,28 +2,47 @@ import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CLIENT_TYPE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; 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.Collections; +import java.util.HashMap; 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.clienttype.ClientType; import seedu.address.model.person.Address; +import seedu.address.model.person.Description; 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.reminder.Reminder; /** * Parses input arguments and creates a new AddCommand object */ public class AddCommandParser implements Parser { + private static final String MESSAGE_MISSING_FIELD = "The following field for add command is missing: %s"; + private static final String MESSAGE_MULTIPLE_MISSING_FIELDS = + "The following fields for add command are missing: %s"; + + // Map to store prefix to field description mapping + private static final HashMap FIELD_DESCRIPTIONS = new HashMap<>(); + static { + FIELD_DESCRIPTIONS.put(PREFIX_NAME, "NAME"); + FIELD_DESCRIPTIONS.put(PREFIX_PHONE, "PHONE"); + FIELD_DESCRIPTIONS.put(PREFIX_EMAIL, "EMAIL"); + FIELD_DESCRIPTIONS.put(PREFIX_ADDRESS, "ADDRESS"); + FIELD_DESCRIPTIONS.put(PREFIX_CLIENT_TYPE, "CLIENT_TYPE"); + FIELD_DESCRIPTIONS.put(PREFIX_DESCRIPTION, "DESCRIPTION"); + } + /** * Parses the given {@code String} of arguments in the context of the AddCommand * and returns an AddCommand object for execution. @@ -31,21 +50,28 @@ public class AddCommandParser implements Parser { */ public AddCommand parse(String args) throws ParseException { ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + PREFIX_CLIENT_TYPE, PREFIX_DESCRIPTION); - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) - || !argMultimap.getPreamble().isEmpty()) { + // Check for missing fields + arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, + PREFIX_EMAIL, PREFIX_CLIENT_TYPE, PREFIX_DESCRIPTION); + + // Only check for preamble if there are no missing fields + if (!argMultimap.getPreamble().isEmpty()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); } - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + PREFIX_DESCRIPTION); 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); + Set clientTypeList = ParserUtil.parseClientTypes(argMultimap.getAllValues(PREFIX_CLIENT_TYPE)); + Description description = ParserUtil.parseDescription(argMultimap.getValue(PREFIX_DESCRIPTION).get()); + Set reminders = Collections.emptySet(); + Person person = new Person(name, phone, email, address, clientTypeList, description, reminders); return new AddCommand(person); } @@ -54,8 +80,32 @@ public AddCommand parse(String args) throws ParseException { * 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()); + private static void arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) + throws ParseException { + //return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + + StringBuilder missingFields = new StringBuilder(); + + for (Prefix prefix : prefixes) { + if (prefix == PREFIX_CLIENT_TYPE) { + if (argumentMultimap.getAllValues(prefix).isEmpty()) { + missingFields.append(String.format("%s%s ", + prefix.getPrefix(), FIELD_DESCRIPTIONS.get(prefix))); + } + } else if (!argumentMultimap.getValue(prefix).isPresent()) { + missingFields.append(String.format("%s%s ", + prefix.getPrefix(), FIELD_DESCRIPTIONS.get(prefix))); + } + } + + // If any fields are missing, throw exception with specific message + if (missingFields.length() > 0) { + String missingFieldsStr = missingFields.toString().trim(); + String message = missingFieldsStr.contains(" ") + ? String.format(MESSAGE_MULTIPLE_MISSING_FIELDS, missingFieldsStr) + : String.format(MESSAGE_MISSING_FIELD, missingFieldsStr); + throw new ParseException(message); + } } } diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf119..48540da60d5 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -10,6 +10,7 @@ public class CliSyntax { 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_CLIENT_TYPE = new Prefix("c/"); + public static final Prefix PREFIX_DESCRIPTION = new Prefix("d/"); + public static final Prefix PREFIX_DATE_TIME = new Prefix("dt/"); } diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/ClientHubParser.java similarity index 56% rename from src/main/java/seedu/address/logic/parser/AddressBookParser.java rename to src/main/java/seedu/address/logic/parser/ClientHubParser.java index 3149ee07e0b..45dc2e378c3 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/ClientHubParser.java @@ -14,21 +14,33 @@ import seedu.address.logic.commands.DeleteCommand; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.FindAddressCommand; +import seedu.address.logic.commands.FindClientTypeCommand; import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FindNameCommand; +import seedu.address.logic.commands.FindPhoneCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.SortCommand; +import seedu.address.logic.commands.ViewCommand; +import seedu.address.logic.commands.reminder.AddReminderCommand; +import seedu.address.logic.commands.reminder.DeleteReminderCommand; +import seedu.address.logic.commands.reminder.EditReminderCommand; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.logic.parser.reminder.AddReminderCommandParser; +import seedu.address.logic.parser.reminder.DeleteReminderCommandParser; +import seedu.address.logic.parser.reminder.EditReminderCommandParser; /** * Parses user input. */ -public class AddressBookParser { +public class ClientHubParser { /** * Used for initial separation of command word and args. */ private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); - private static final Logger logger = LogsCenter.getLogger(AddressBookParser.class); + private static final Logger logger = LogsCenter.getLogger(ClientHubParser.class); /** * Parses user input into command for execution. @@ -53,30 +65,57 @@ public Command parseCommand(String userInput) throws ParseException { switch (commandWord) { - case AddCommand.COMMAND_WORD: + case AddCommand.COMMAND_WORD, AddCommand.SHORT_COMMAND_WORD: return new AddCommandParser().parse(arguments); case EditCommand.COMMAND_WORD: return new EditCommandParser().parse(arguments); - case DeleteCommand.COMMAND_WORD: + case DeleteCommand.COMMAND_WORD, DeleteCommand.SHORT_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: + case FindPhoneCommand.COMMAND_WORD: + return new FindPhoneCommandParser().parse(arguments); + + case FindClientTypeCommand.COMMAND_WORD: + return new FindClientTypeCommandParser().parse(arguments); + + case FindNameCommand.COMMAND_WORD: + return new FindNameCommandParser().parse(arguments); + + case FindAddressCommand.COMMAND_WORD: + return new FindAddressCommandParser().parse(arguments); + + case ListCommand.COMMAND_WORD, ListCommand.SHORT_COMMAND_WORD: return new ListCommand(); + case SortCommand.COMMAND_WORD, SortCommand.SHORT_COMMAND_WORD: + return new SortCommand(); + + case ViewCommand.COMMAND_WORD, ViewCommand.SHORT_COMMAND_WORD: + return new ViewCommandParser().parse(arguments); + + case ClearCommand.COMMAND_WORD: + return new ClearCommand(); + case ExitCommand.COMMAND_WORD: return new ExitCommand(); case HelpCommand.COMMAND_WORD: return new HelpCommand(); + case DeleteReminderCommand.COMMAND_WORD, DeleteReminderCommand.COMMAND_WORD_SHORT: + return new DeleteReminderCommandParser().parse(arguments); + + case AddReminderCommand.COMMAND_WORD, AddReminderCommand.COMMAND_WORD_SHORT: + return new AddReminderCommandParser().parse(arguments); + + case EditReminderCommand.COMMAND_WORD, EditReminderCommand.COMMAND_WORD_SHORT: + return new EditReminderCommandParser().parse(arguments); + default: logger.finer("This user input caused a ParseException: " + userInput); throw new ParseException(MESSAGE_UNKNOWN_COMMAND); diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java index 3527fe76a3e..95f007e43c2 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java @@ -2,9 +2,12 @@ import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import seedu.address.commons.core.index.Index; +import java.util.Arrays; + import seedu.address.logic.commands.DeleteCommand; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Name; +import seedu.address.model.person.NameContainsKeywordsDeletePredicate; /** * Parses input arguments and creates a new DeleteCommand object @@ -17,13 +20,19 @@ public class DeleteCommandParser implements Parser { * @throws ParseException if the user input does not conform the expected format */ public DeleteCommand parse(String args) throws ParseException { - try { - Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); - } catch (ParseException pe) { + String personNames = args.trim(); + if (!Name.isValidName(personNames) && !personNames.endsWith("$")) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteCommand.MESSAGE_USAGE)); + } + if (personNames.isEmpty()) { throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE)); } + + String[] nameKeywords = personNames.split("\\s+"); + return new DeleteCommand(new NameContainsKeywordsDeletePredicate(Arrays.asList(nameKeywords))); } + } diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index 46b3309a78b..9a8bccf94b2 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -3,21 +3,22 @@ import static java.util.Objects.requireNonNull; import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CLIENT_TYPE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; 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 seedu.address.commons.core.index.Index; +import seedu.address.logic.Messages; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.tag.Tag; +import seedu.address.model.clienttype.ClientType; /** * Parses input arguments and creates a new EditCommand object @@ -32,7 +33,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_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + PREFIX_CLIENT_TYPE, PREFIX_DESCRIPTION); Index index; @@ -42,7 +44,8 @@ public EditCommand parse(String args) throws ParseException { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); } - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + PREFIX_DESCRIPTION); EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); @@ -58,7 +61,13 @@ public EditCommand parse(String args) throws ParseException { if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); + parseClientTypesForEdit(argMultimap.getAllValues(PREFIX_CLIENT_TYPE)) + .ifPresent(editPersonDescriptor::setClientTypes); + + if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) { + editPersonDescriptor.setDescription(ParserUtil.parseDescription(argMultimap + .getValue(PREFIX_DESCRIPTION).get())); + } if (!editPersonDescriptor.isAnyFieldEdited()) { throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); @@ -68,18 +77,22 @@ public EditCommand parse(String args) throws ParseException { } /** - * 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. + * Parses {@code Collection clientTypes} into a {@code Set} + * if {@code clientTypes} is non-empty. + * If {@code clientTypes} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero clientTypes. */ - private Optional> parseTagsForEdit(Collection tags) throws ParseException { - assert tags != null; + private Optional> parseClientTypesForEdit(Collection clientTypes) throws ParseException { + assert clientTypes != null; - if (tags.isEmpty()) { + if (clientTypes.isEmpty()) { return Optional.empty(); } - Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; - return Optional.of(ParserUtil.parseTags(tagSet)); + if (clientTypes.size() == 1 && clientTypes.contains("")) { + throw new ParseException(Messages.MESSAGE_NO_CLIENT_TYPE); + } else { + return Optional.of(ParserUtil.parseClientTypes(clientTypes)); + } } } diff --git a/src/main/java/seedu/address/logic/parser/FindAddressCommandParser.java b/src/main/java/seedu/address/logic/parser/FindAddressCommandParser.java new file mode 100644 index 00000000000..e81bb3b0692 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindAddressCommandParser.java @@ -0,0 +1,28 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.FindAddressCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.AddressContainsKeywordsPredicate; + +/** + * Parses input arguments and creates a new FindAddressCommand object + */ +public class FindAddressCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FindAddressCommand + * and returns a FindAddressCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindAddressCommand parse(String args) throws ParseException { + String addressKeywords = args.trim(); + if (addressKeywords.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindAddressCommand.MESSAGE_USAGE)); + } + + return new FindAddressCommand(new AddressContainsKeywordsPredicate(addressKeywords)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/FindClientTypeCommandParser.java b/src/main/java/seedu/address/logic/parser/FindClientTypeCommandParser.java new file mode 100644 index 00000000000..885d2e12a80 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindClientTypeCommandParser.java @@ -0,0 +1,39 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; + +import seedu.address.logic.commands.FindClientTypeCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.ClientTypeContainsKeywordsPredicate; + +/** + * Parses input arguments and creates a new FindClientTypeCommand object + */ +public class FindClientTypeCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FindClientTypeCommand + * and returns a FindClientTypeCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindClientTypeCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindClientTypeCommand.MESSAGE_USAGE)); + } + + // Check for special characters - only allow alphanumeric and whitespace + if (!trimmedArgs.matches("^[a-zA-Z0-9\\s]+$")) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindClientTypeCommand.MESSAGE_USAGE)); + } + + String[] clientTypeKeywords = trimmedArgs.split("\\s+"); + + return new FindClientTypeCommand(new ClientTypeContainsKeywordsPredicate(Arrays.asList(clientTypeKeywords))); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java index 2867bde857b..5723ba2c91d 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -3,31 +3,106 @@ import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import java.util.Arrays; +import java.util.List; +import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.FindAddressCommand; +import seedu.address.logic.commands.FindClientTypeCommand; +//import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FindNameCommand; +import seedu.address.logic.commands.FindPhoneCommand; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.AddressContainsKeywordsPredicate; +import seedu.address.model.person.ClientTypeContainsKeywordsPredicate; import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.PhoneBeginsWithKeywordPredicate; /** - * Parses input arguments and creates a new FindCommand object + * Parses input arguments and creates a new FindCommand object based on the prefix provided. + *

+ * If the input starts with the prefix: + * - "n/" (name): a {@code FindNameCommand} will be returned, which finds persons whose names contain + * the specified keywords. + * - "p/" (phone): a {@code FindPhoneCommand} will be returned, which finds persons whose phone numbers begin + * with the specified keyword. + * - "a/" (address): a {@code FindAddressCommand} will be returned, which finds persons whose addresses + * contain the specified keywords. + * - "c/" (client type): a {@code FindClientTypeCommand} will be returned, which finds persons + * based on their client type. + *

+ * The matching for name, address, and client type is case-insensitive, and the keywords can be separated by whitespace. + * However, the phone number is treated as a single string. + *

+ * The parser checks if the input is correctly formatted and throws a {@code ParseException} if any input is invalid. */ -public class FindCommandParser implements Parser { +public class FindCommandParser implements Parser { + + private static final String PREFIX_NAME = "n/"; + private static final String PREFIX_PHONE = "p/"; + private static final String PREFIX_ADDRESS = "a/"; + private static final String PREFIX_CLIENT_TYPE = "c/"; /** * 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 + * and returns a Command object for execution. + * + * @param args The input arguments string containing the search prefix and keyword(s). + * @return A Command object (FindNameCommand, FindPhoneCommand, FindAddressCommand, or FindClientTypeCommand) + * depending on the prefix provided. + * @throws ParseException if the user input does not conform to the expected format, + * such as missing keywords or invalid prefixes. */ - public FindCommand parse(String args) throws ParseException { + public Command parse(String args) throws ParseException { String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { throw new ParseException( String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); } - String[] nameKeywords = trimmedArgs.split("\\s+"); + // Check if the input starts with "n/", "p/", "a/", or "c/". + if (trimmedArgs.startsWith(PREFIX_NAME)) { + String stringName = trimmedArgs.substring(PREFIX_NAME.length()).trim(); + String[] nameKeywords = trimmedArgs.substring(PREFIX_NAME.length()).trim().split("\\s+"); + if (nameKeywords.length == 0 || nameKeywords[0].isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindNameCommand.MESSAGE_USAGE)); + } + if (!stringName.matches("^[a-zA-Z\\s\\(\\)]+$")) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindNameCommand.MESSAGE_USAGE)); + } + return new FindNameCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); - } + } else if (trimmedArgs.startsWith(PREFIX_PHONE)) { + String phoneKeywords = trimmedArgs.substring(PREFIX_PHONE.length()).trim(); + if (phoneKeywords.isEmpty() || !phoneKeywords.matches("\\d+")) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindPhoneCommand.MESSAGE_USAGE)); + } + return new FindPhoneCommand(new PhoneBeginsWithKeywordPredicate(phoneKeywords)); + + } else if (trimmedArgs.startsWith(PREFIX_ADDRESS)) { + String addressKeywords = trimmedArgs.substring(PREFIX_ADDRESS.length()).trim(); + if (addressKeywords.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindAddressCommand.MESSAGE_USAGE)); + } + return new FindAddressCommand(new AddressContainsKeywordsPredicate(addressKeywords)); + + } else if (trimmedArgs.startsWith(PREFIX_CLIENT_TYPE)) { + String clientTypeKeywords = trimmedArgs.substring(PREFIX_CLIENT_TYPE.length()).trim(); + if (clientTypeKeywords.isEmpty() || !clientTypeKeywords.matches("^[a-zA-Z0-9\\s]+$")) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindClientTypeCommand.MESSAGE_USAGE)); + } + String[] clientTypeKeywordsArr = clientTypeKeywords.split("\\s+"); + return new FindClientTypeCommand(new ClientTypeContainsKeywordsPredicate(List.of(clientTypeKeywordsArr))); + } else { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + } + } } diff --git a/src/main/java/seedu/address/logic/parser/FindNameCommandParser.java b/src/main/java/seedu/address/logic/parser/FindNameCommandParser.java new file mode 100644 index 00000000000..4b3daab8c07 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindNameCommandParser.java @@ -0,0 +1,36 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; + +import seedu.address.logic.commands.FindNameCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.NameContainsKeywordsPredicate; + +/** + * Parses input arguments and creates a new FindNameCommand object + */ +public class FindNameCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FindNameCommand + * and returns a FindNameCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindNameCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindNameCommand.MESSAGE_USAGE)); + } + if (!trimmedArgs.matches("^[a-zA-Z()/$\\s]+$")) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindNameCommand.MESSAGE_USAGE)); + } + + String[] nameKeywords = trimmedArgs.split("\\s+"); + + return new FindNameCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + } +} diff --git a/src/main/java/seedu/address/logic/parser/FindPhoneCommandParser.java b/src/main/java/seedu/address/logic/parser/FindPhoneCommandParser.java new file mode 100644 index 00000000000..5ff0c7b7982 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindPhoneCommandParser.java @@ -0,0 +1,37 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.FindPhoneCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.PhoneBeginsWithKeywordPredicate; + +/** + * Parses input arguments and creates a new FindPhoneCommand object + */ +public class FindPhoneCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FindCommand + * and returns a FindPhoneNumberCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindPhoneCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + + if (trimmedArgs.isEmpty() || !trimmedArgs.matches("\\d+")) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindPhoneCommand.MESSAGE_USAGE)); + } + + String[] phoneNumber = trimmedArgs.split("\\s+"); + + if (phoneNumber.length != 1) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindPhoneCommand.MESSAGE_USAGE)); + } + + return new FindPhoneCommand(new PhoneBeginsWithKeywordPredicate(phoneNumber[0])); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..4e0f53c3eac 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -2,6 +2,9 @@ import static java.util.Objects.requireNonNull; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -9,11 +12,13 @@ import seedu.address.commons.core.index.Index; import seedu.address.commons.util.StringUtil; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.clienttype.ClientType; import seedu.address.model.person.Address; +import seedu.address.model.person.Description; 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.reminder.ReminderDescription; /** * Contains utility methods used for parsing strings in the various *Parser classes. @@ -21,6 +26,11 @@ public class ParserUtil { public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; + public static final String MESSAGE_INVALID_DATETIME = "DateTime is not in the correct format. " + + "Please use the format: yyyy-MM-dd HH:mm"; + + // Formatter for the required DateTime format + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be @@ -96,29 +106,86 @@ public static Email parseEmail(String email) throws ParseException { } /** - * Parses a {@code String tag} into a {@code Tag}. + * Parses a {@code String clientType} into a {@code ClientType}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code clientType} is invalid. + */ + public static ClientType parseClientType(String clientType) throws ParseException { + requireNonNull(clientType); + String trimmedClientType = clientType.trim(); + if (!ClientType.isValidClientTypeName(trimmedClientType)) { + throw new ParseException(ClientType.MESSAGE_CONSTRAINTS); + } + return new ClientType(trimmedClientType); + } + + /** + * Parses {@code Collection clientTypes} into a {@code Set}. + */ + public static Set parseClientTypes(Collection clientTypes) throws ParseException { + requireNonNull(clientTypes); + final Set clientTypeSet = new HashSet<>(); + for (String clientTypeName : clientTypes) { + clientTypeSet.add(parseClientType(clientTypeName)); + } + return clientTypeSet; + } + + /** + * Parses a {@code String description} into a {@code Description}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code tag} is invalid. + * @param description the string representation of a description + * @return a {@code Description} corresponding to the given string + * @throws ParseException if the given {@code description} 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); + public static Description parseDescription(String description) throws ParseException { + requireNonNull(description); + String trimmedDescription = description.trim(); + if (!Description.isValidDescription(trimmedDescription)) { + throw new ParseException(Description.MESSAGE_CONSTRAINTS); } - return new Tag(trimmedTag); + return new Description(trimmedDescription); } /** - * Parses {@code Collection tags} into a {@code Set}. + * Parses a {@code String dateTime} into a {@code LocalDateTime}. + * Leading and trailing whitespaces will be trimmed. + * + * @param dateTime the string representation of date and time in yyyy-MM-dd HH:mm format + * @return a {@code LocalDateTime} corresponding to the given string + * @throws ParseException if the given {@code dateTime} is not in the correct format + */ + public static LocalDateTime parseDateTime(String dateTime) throws ParseException { + requireNonNull(dateTime); + String trimmedDateTime = dateTime.trim(); + try { + return LocalDateTime.parse(trimmedDateTime, DATE_TIME_FORMATTER); + } catch (DateTimeParseException e) { + throw new ParseException(MESSAGE_INVALID_DATETIME); + } + } + + /** + * Parses the given {@code String} description and returns a {@code ReminderDescription} object. + * + *

The method trims the input description and validates it. If the description is valid, + * it returns a new {@code ReminderDescription} instance with the trimmed description. + * If the description is invalid, it throws a {@code ParseException} with an appropriate message. + * + * @param description The {@code String} description to be parsed. + * @return A {@code ReminderDescription} object containing the trimmed description. + * @throws ParseException if the given description is invalid according to {@code ReminderDescription. + * MESSAGE_CONSTRAINTS}. + * @throws NullPointerException if {@code description} is null. */ - 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 ReminderDescription parseReminderDescription(String description) throws ParseException { + requireNonNull(description); + String trimmedDescription = description.trim(); + if (!Description.isValidDescription(trimmedDescription)) { + throw new ParseException(ReminderDescription.MESSAGE_CONSTRAINTS); } - return tagSet; + return new ReminderDescription(trimmedDescription); } } diff --git a/src/main/java/seedu/address/logic/parser/ViewCommandParser.java b/src/main/java/seedu/address/logic/parser/ViewCommandParser.java new file mode 100644 index 00000000000..64e2272bf2b --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ViewCommandParser.java @@ -0,0 +1,41 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; + +import seedu.address.logic.commands.ViewCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.NameContainsKeywordsPredicate; + +/** + * 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 + */ + public ViewCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + + // Check if the user input is empty, if it is, throw an error + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE)); + } + + // Check for special characters - only allow letters, white space and slashes (For viewing exact names). + if (!trimmedArgs.matches("^[a-zA-Z()/$\\s]+$")) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE)); + } + + String[] nameKeywords = trimmedArgs.split("\\s+"); + + return new ViewCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/reminder/AddReminderCommandParser.java b/src/main/java/seedu/address/logic/parser/reminder/AddReminderCommandParser.java new file mode 100644 index 00000000000..82f897cd5f9 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/reminder/AddReminderCommandParser.java @@ -0,0 +1,66 @@ +package seedu.address.logic.parser.reminder; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_TIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; + +import java.time.LocalDateTime; +import java.util.stream.Stream; + +import seedu.address.logic.commands.reminder.AddReminderCommand; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.Prefix; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.reminder.Reminder; +import seedu.address.model.reminder.ReminderDescription; + +/** + * Parses input arguments and creates a new AddReminderCommand object. + */ +public class AddReminderCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddReminderCommand + * and returns an AddReminderCommand object for execution. + * @throws ParseException if the user input does not conform to the expected format + */ + @Override + public AddReminderCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_DATE_TIME, PREFIX_DESCRIPTION); + + // To check if fields are input correctly + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_DATE_TIME, PREFIX_DESCRIPTION) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddReminderCommand.MESSAGE_USAGE)); + } + + // Verify that each prefix is only used once + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_DATE_TIME, PREFIX_DESCRIPTION); + + // Parsing each field using the updated ParserUtil methods + String person = argMultimap.getValue(PREFIX_NAME).get(); + LocalDateTime dateTime = ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_DATE_TIME).get()); + ReminderDescription description = ParserUtil.parseReminderDescription(argMultimap + .getValue(PREFIX_DESCRIPTION).get()); + + // Creating a Reminder object with the parsed data + Reminder reminder = new Reminder(person, dateTime, description); + + return new AddReminderCommand(reminder); + } + + /** + * 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/reminder/DeleteReminderCommandParser.java b/src/main/java/seedu/address/logic/parser/reminder/DeleteReminderCommandParser.java new file mode 100644 index 00000000000..aa82ff4dd2d --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/reminder/DeleteReminderCommandParser.java @@ -0,0 +1,27 @@ +package seedu.address.logic.parser.reminder; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.reminder.DeleteReminderCommand; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses user input to create a new {@code DeleteReminderCommand}. + */ +public class DeleteReminderCommandParser implements Parser { + + @Override + public DeleteReminderCommand parse(String userInput) throws ParseException { + try { + Index index = ParserUtil.parseIndex(userInput); + return new DeleteReminderCommand(index); + } catch (ParseException e) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteReminderCommand.MESSAGE_USAGE), e); + } + + } +} diff --git a/src/main/java/seedu/address/logic/parser/reminder/EditReminderCommandParser.java b/src/main/java/seedu/address/logic/parser/reminder/EditReminderCommandParser.java new file mode 100644 index 00000000000..2c7bf3093d4 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/reminder/EditReminderCommandParser.java @@ -0,0 +1,57 @@ +package seedu.address.logic.parser.reminder; +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE_TIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DESCRIPTION; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.EditCommand; +import seedu.address.logic.commands.reminder.EditReminderCommand; +import seedu.address.logic.commands.reminder.EditReminderCommand.EditReminderFields; +import seedu.address.logic.parser.ArgumentMultimap; +import seedu.address.logic.parser.ArgumentTokenizer; +import seedu.address.logic.parser.Parser; +import seedu.address.logic.parser.ParserUtil; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses user input to create a new {@code EditReminderCommand}. + */ +public class EditReminderCommandParser implements Parser { + public EditReminderCommandParser() { + } + + @Override + public EditReminderCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_DATE_TIME, PREFIX_DESCRIPTION); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + EditReminderCommand.MESSAGE_USAGE), pe); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_DATE_TIME, PREFIX_DESCRIPTION); + + EditReminderFields editReminderFields = new EditReminderFields(); + + if (argMultimap.getValue(PREFIX_DATE_TIME).isPresent()) { + editReminderFields.setDateTime(ParserUtil.parseDateTime(argMultimap.getValue(PREFIX_DATE_TIME).get())); + } + if (argMultimap.getValue(PREFIX_DESCRIPTION).isPresent()) { + editReminderFields.setDescription(ParserUtil.parseReminderDescription( + argMultimap.getValue(PREFIX_DESCRIPTION).get())); + } + + if (!editReminderFields.isAnyFieldEdited()) { + throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); + } + + return new EditReminderCommand(index, editReminderFields); + } +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java deleted file mode 100644 index 73397161e84..00000000000 --- a/src/main/java/seedu/address/model/AddressBook.java +++ /dev/null @@ -1,130 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import javafx.collections.ObservableList; -import seedu.address.commons.util.ToStringBuilder; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; - -/** - * Wraps all data at the address-book level - * Duplicates are not allowed (by .isSamePerson comparison) - */ -public class AddressBook implements ReadOnlyAddressBook { - - private final UniquePersonList persons; - - /* - * 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. - */ - { - persons = new UniquePersonList(); - } - - public AddressBook() {} - - /** - * Creates an AddressBook using the Persons in the {@code toBeCopied} - */ - public AddressBook(ReadOnlyAddressBook toBeCopied) { - this(); - resetData(toBeCopied); - } - - //// list overwrite operations - - /** - * Replaces the contents of the person list with {@code persons}. - * {@code persons} must not contain duplicate persons. - */ - public void setPersons(List persons) { - this.persons.setPersons(persons); - } - - /** - * Resets the existing data of this {@code AddressBook} with {@code newData}. - */ - public void resetData(ReadOnlyAddressBook newData) { - requireNonNull(newData); - - setPersons(newData.getPersonList()); - } - - //// person-level operations - - /** - * Returns true if a person with the same identity as {@code person} exists in the address book. - */ - public boolean hasPerson(Person person) { - requireNonNull(person); - return persons.contains(person); - } - - /** - * Adds a person to the address book. - * The person must not already exist in the address book. - */ - public void addPerson(Person p) { - persons.add(p); - } - - /** - * Replaces the given person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. - */ - public void setPerson(Person target, Person editedPerson) { - requireNonNull(editedPerson); - - persons.setPerson(target, editedPerson); - } - - /** - * Removes {@code key} from this {@code AddressBook}. - * {@code key} must exist in the address book. - */ - public void removePerson(Person key) { - persons.remove(key); - } - - //// util methods - - @Override - public String toString() { - return new ToStringBuilder(this) - .add("persons", persons) - .toString(); - } - - @Override - public ObservableList getPersonList() { - return persons.asUnmodifiableObservableList(); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof AddressBook)) { - return false; - } - - AddressBook otherAddressBook = (AddressBook) other; - return persons.equals(otherAddressBook.persons); - } - - @Override - public int hashCode() { - return persons.hashCode(); - } -} diff --git a/src/main/java/seedu/address/model/ClientHub.java b/src/main/java/seedu/address/model/ClientHub.java new file mode 100644 index 00000000000..4ad74834863 --- /dev/null +++ b/src/main/java/seedu/address/model/ClientHub.java @@ -0,0 +1,250 @@ +package seedu.address.model; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.stream.Collectors; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.Person; +import seedu.address.model.person.UniquePersonList; +import seedu.address.model.reminder.Reminder; +import seedu.address.model.reminder.ReminderList; + + +/** + * Wraps all data at the address-book level. + * Duplicates are not allowed (by .isSamePerson comparison). + */ +public class ClientHub implements ReadOnlyClientHub { + + private final UniquePersonList persons; + private final ReminderList reminders; + + /* + * 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 aother ways to avoid duplication + * among constructors. + */ + { + persons = new UniquePersonList(); + reminders = new ReminderList(); + } + + /** + * Creates an empty ClientHub. + */ + public ClientHub() {} + + /** + * Creates a ClientHub using the data from the given {@code toBeCopied}. + * + * @param toBeCopied The ReadOnlyClientHub to copy data from. + */ + public ClientHub(ReadOnlyClientHub toBeCopied) { + this(); + resetData(toBeCopied); + } + + //// list overwrite operations + + /** + * Replaces the contents of the person list with the specified {@code persons}. + * The list must not contain duplicate persons. + * + * @param persons The list of persons to set. + */ + public void setPersons(List persons) { + this.persons.setPersons(persons); + } + + /** + * Replaces the contents of the reminder list with the specified {@code reminders}. + * The list must not contain duplicate reminders. + * + * @param reminders The list of reminders to set. + */ + public void setReminders(List reminders) { + this.reminders.setReminders(reminders); + } + + /** + * Resets the existing data of this ClientHub with the specified {@code newData}. + * + * @param newData The new data to reset with. + */ + public void resetData(ReadOnlyClientHub newData) { + requireNonNull(newData); + + setPersons(newData.getPersonList()); + setReminders(newData.getReminderList()); + } + + //// person-level operations + + /** + * Checks if a person with the same identity as the specified {@code person} exists in the address book. + * + * @param person The person to check. + * @return True if the person exists, false otherwise. + */ + public boolean hasPerson(Person person) { + requireNonNull(person); + return persons.contains(person); + } + + /** + * Checks if a reminder with the same identity as the specified {@code reminder} exists in the address book. + * + * @param reminder The reminder to check. + * @return True if the reminder exists, false otherwise. + */ + public boolean hasReminder(Reminder reminder) { + requireNonNull(reminder); + return reminders.contains(reminder); + } + + /** + * Adds a person to the address book. + * The person must not already exist in the address book. + * + * @param p The person to add. + */ + public void addPerson(Person p) { + persons.add(p); + } + + /** + * Adds a reminder to the address book. + * The reminder must not already exist in the address book. + * + * @param r The reminder to add. + */ + public void addReminder(Reminder r) { + reminders.add(r); + } + + /** + * Replaces the specified {@code target} person in the list with {@code editedPerson}. + * The {@code target} must exist in the address book, and the {@code editedPerson} must not have the + * same identity as another existing person in the address book. + * + * @param target The person to replace. + * @param editedPerson The new person to replace with. + */ + public void setPerson(Person target, Person editedPerson) { + requireNonNull(editedPerson); + + persons.setPerson(target, editedPerson); + } + + /** + * Replaces the specified {@code target} reminder in the list with {@code editedReminder}. + * The {@code target} must exist in the address book. + * + * @param target The reminder to replace. + * @param editedReminder The new reminder to replace with. + */ + public void setReminder(Reminder target, Reminder editedReminder) { + requireNonNull(editedReminder); + + reminders.setReminder(target, editedReminder); + } + + /** + * Removes the specified {@code key} person from this ClientHub. + * The person must exist in the address book. + * + * @param key The person to remove. + */ + public void removePerson(Person key) { + persons.remove(key); + } + + /** + * Removes the specified {@code key} reminder from this ClientHub. + * The reminder must exist in the address book. + * + * @param key The reminder to remove. + */ + public void removeReminder(Reminder key) { + reminders.remove(key); + } + + //// util methods + + /** + * Returns a string representation of the ClientHub, including the person and reminder lists. + * + * @return A string representation of the ClientHub. + */ + @Override + public String toString() { + return new ToStringBuilder(this) + .add("persons", persons) + .add("reminders", reminders) + .toString(); + } + + /** + * Returns the person list as an unmodifiable observable list. + * + * @return The person list. + */ + @Override + public ObservableList getPersonList() { + return persons.asUnmodifiableObservableList(); + } + + /** + * Returns the reminder list as an unmodifiable observable list. + * + * @return The reminder list. + */ + public ObservableList getReminders() { + return reminders.asUnmodifiableObservableList(); + } + + public ObservableList getReminderList() { + ObservableList temp = FXCollections.observableArrayList( + persons.stream() + .flatMap(person -> person.getReminders().stream()) + .collect(Collectors.toList())); + reminders.setReminders(temp); + return reminders.asUnmodifiableObservableList(); + } + + /** + * Checks if this ClientHub is equal to another object. + * + * @param other The object to compare with. + * @return True if equal, false otherwise. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof ClientHub)) { + return false; + } + + ClientHub otherClientHub = (ClientHub) other; + return persons.equals(otherClientHub.persons) && reminders.equals(otherClientHub.reminders); + } + + /** + * Returns the hash code of this ClientHub. + * + * @return The hash code. + */ + @Override + public int hashCode() { + return persons.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..3129f0ff1a1 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -1,11 +1,13 @@ package seedu.address.model; import java.nio.file.Path; +import java.util.Comparator; 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.reminder.Reminder; /** * The API of the Model component. @@ -13,6 +15,7 @@ public interface Model { /** {@code Predicate} that always evaluate to true */ Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + Predicate PREDICATE_SHOW_ALL_REMINDERS = unused -> true; /** * Replaces user prefs data with the data in {@code userPrefs}. @@ -37,38 +40,54 @@ public interface Model { /** * Returns the user prefs' address book file path. */ - Path getAddressBookFilePath(); + Path getClientHubFilePath(); /** * Sets the user prefs' address book file path. */ - void setAddressBookFilePath(Path addressBookFilePath); + void setClientHubFilePath(Path clientHubFilePath); /** - * Replaces address book data with the data in {@code addressBook}. + * Replaces address book data with the data in {@code clientHub}. */ - void setAddressBook(ReadOnlyAddressBook addressBook); + void setClientHub(ReadOnlyClientHub clientHub); - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); + /** Returns the ClientHub */ + ReadOnlyClientHub getClientHub(); /** * Returns true if a person with the same identity as {@code person} exists in the address book. */ boolean hasPerson(Person person); + /** + * Returns true if a reminder with the same identity as {@code reminder} exists in the address book. + */ + boolean hasReminder(Reminder reminder); + /** * Deletes the given person. * The person must exist in the address book. */ void deletePerson(Person target); + /** + * Deletes the given reminder. + * The reminder must exist in the address book. + */ + void deleteReminder(Reminder target); + /** * Adds the given person. * {@code person} must not already exist in the address book. */ void addPerson(Person person); + /** + * Adds the given reminder. + * {@code reminder} must not already exist in the address book. + */ + void addReminder(Reminder reminder); /** * Replaces the given person {@code target} with {@code editedPerson}. * {@code target} must exist in the address book. @@ -76,12 +95,49 @@ public interface Model { */ void setPerson(Person target, Person editedPerson); + /** + * Replaces the given reminder {@code target} with {@code editedReminder}. + * {@code target} must exist in the address book. + * The reminder identity of {@code editedReminder} + */ + void setReminder(Reminder target, Reminder editedReminder); + /** + * Returns the number of persons in the address book. + */ + int getDisplayPersonsListSize(); + + /** + * Returns the number of reminders in the address book. + */ + int getDisplayRemindersListSize(); + /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); + ObservableList getDisplayPersons(); + + /** Returns an unmodifiable view of the filtered reminder list */ + ObservableList getDisplayReminders(); + + /** Updates the current list to display all contacts in the list */ + void updateUnfilteredList(); + + /** Updates the current list to display all reminders in the list */ + void updateUnfilteredReminderList(); /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * Updates the current list to filter by the given {@code predicate}. * @throws NullPointerException if {@code predicate} is null. */ void updateFilteredPersonList(Predicate predicate); + + /** + * Updates the current list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredReminderList(); + + /** + * Updates the current list to sort by the given {@code comparator}. + * @throws NullPointerException if {@code comparator} is null. + */ + void updateSortedPersonList(Comparator comparator); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 57bc563fde6..15fa296987c 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -4,14 +4,17 @@ import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.nio.file.Path; +import java.util.Comparator; import java.util.function.Predicate; import java.util.logging.Logger; import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; +import javafx.collections.transformation.SortedList; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; import seedu.address.model.person.Person; +import seedu.address.model.reminder.Reminder; /** * Represents the in-memory model of the address book data. @@ -19,25 +22,40 @@ public class ModelManager implements Model { private static final Logger logger = LogsCenter.getLogger(ModelManager.class); - private final AddressBook addressBook; + private final ClientHub clientHub; private final UserPrefs userPrefs; + private final ObservableList originalPersons; private final FilteredList filteredPersons; + private final SortedList sortedPersons; + private final ObservableList originalReminders; + private final FilteredList filteredReminders; + private final SortedList sortedReminders; + /** - * Initializes a ModelManager with the given addressBook and userPrefs. + * Initializes a ModelManager with the given clientHub and userPrefs. */ - public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) { - requireAllNonNull(addressBook, userPrefs); + public ModelManager(ReadOnlyClientHub clientHub, ReadOnlyUserPrefs userPrefs) { + requireAllNonNull(clientHub, userPrefs); - logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); + logger.fine("Initializing with clienthub: " + clientHub + " and user prefs " + userPrefs); - this.addressBook = new AddressBook(addressBook); + this.clientHub = new ClientHub(clientHub); this.userPrefs = new UserPrefs(userPrefs); - filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + this.originalPersons = this.clientHub.getPersonList(); + this.filteredPersons = new FilteredList<>(originalPersons); + this.sortedPersons = new SortedList<>(filteredPersons); + + this.originalReminders = this.clientHub.getReminderList(); + this.filteredReminders = new FilteredList<>(originalReminders); + this.sortedReminders = new SortedList<>(filteredReminders); + + filteredPersons.setPredicate(PREDICATE_SHOW_ALL_PERSONS); + updateFilteredReminderList(); } public ModelManager() { - this(new AddressBook(), new UserPrefs()); + this(new ClientHub(), new UserPrefs()); } //=========== UserPrefs ================================================================================== @@ -65,69 +83,150 @@ public void setGuiSettings(GuiSettings guiSettings) { } @Override - public Path getAddressBookFilePath() { - return userPrefs.getAddressBookFilePath(); + public Path getClientHubFilePath() { + return userPrefs.getClientHubFilePath(); } @Override - public void setAddressBookFilePath(Path addressBookFilePath) { - requireNonNull(addressBookFilePath); - userPrefs.setAddressBookFilePath(addressBookFilePath); + public void setClientHubFilePath(Path clientHubFilePath) { + requireNonNull(clientHubFilePath); + userPrefs.setClientHubFilePath(clientHubFilePath); } - //=========== AddressBook ================================================================================ + //=========== ClientHub ================================================================================ @Override - public void setAddressBook(ReadOnlyAddressBook addressBook) { - this.addressBook.resetData(addressBook); + public void setClientHub(ReadOnlyClientHub clientHub) { + this.clientHub.resetData(clientHub); } @Override - public ReadOnlyAddressBook getAddressBook() { - return addressBook; + public ReadOnlyClientHub getClientHub() { + return clientHub; } @Override public boolean hasPerson(Person person) { requireNonNull(person); - return addressBook.hasPerson(person); + return clientHub.hasPerson(person); + } + + @Override + public boolean hasReminder(Reminder reminder) { + requireNonNull(reminder); + return clientHub.hasReminder(reminder); } @Override public void deletePerson(Person target) { - addressBook.removePerson(target); + clientHub.removePerson(target); + } + + @Override + public void deleteReminder(Reminder target) { + clientHub.removeReminder(target); } @Override public void addPerson(Person person) { - addressBook.addPerson(person); + clientHub.addPerson(person); updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); } + @Override + public void addReminder(Reminder reminder) { + clientHub.addReminder(reminder); + updateFilteredReminderList(); + } + @Override public void setPerson(Person target, Person editedPerson) { requireAllNonNull(target, editedPerson); - addressBook.setPerson(target, editedPerson); + clientHub.setPerson(target, editedPerson); } - //=========== Filtered Person List Accessors ============================================================= + @Override + public void setReminder(Reminder target, Reminder editedReminder) { + requireAllNonNull(target, editedReminder); + + clientHub.setReminder(target, editedReminder); + } + + @Override + public int getDisplayPersonsListSize() { + return this.getDisplayPersons().size(); + } + + @Override + public int getDisplayRemindersListSize() { + return this.getDisplayReminders().size(); + } /** * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of - * {@code versionedAddressBook} + * {@code versionedClientHub} */ @Override - public ObservableList getFilteredPersonList() { - return filteredPersons; + public ObservableList getDisplayPersons() { + return sortedPersons; } + @Override + public ObservableList getDisplayReminders() { + return sortedReminders; + } + + //=========== Unfiltered Person List Accessors ============================================================= + + @Override + public void updateUnfilteredList() { + // Reset the filter to show all persons + filteredPersons.setPredicate(PREDICATE_SHOW_ALL_PERSONS); + + // Remove any sorting in the list + sortedPersons.setComparator(null); + + // Force resort to match original list order if needed + sortedPersons.setComparator((p1, p2) -> 0); // Force a resort + sortedPersons.setComparator(null); // Remove the comparator + } + + //=========== Unfiltered Reminder List Accessors ============================================================= + + @Override + public void updateUnfilteredReminderList() { + // Reset the filter to show all reminders + filteredReminders.setPredicate(PREDICATE_SHOW_ALL_REMINDERS); + } + + //=========== Filtered Person List Accessors ============================================================= + @Override public void updateFilteredPersonList(Predicate predicate) { requireNonNull(predicate); filteredPersons.setPredicate(predicate); } + //=========== Filtered Reminder List Accessors ============================================================= + + @Override + public void updateFilteredReminderList() { + filteredReminders.setPredicate(PREDICATE_SHOW_ALL_REMINDERS); + + Comparator reminderComparator = Comparator.comparing(Reminder::getDateTime); + + sortedReminders.setComparator(reminderComparator); + } + + //=========== Sorted Person List Accessors ============================================================= + + @Override + public void updateSortedPersonList(Comparator comparator) { + requireNonNull(comparator); + sortedPersons.setComparator(comparator); + } + @Override public boolean equals(Object other) { if (other == this) { @@ -140,9 +239,13 @@ public boolean equals(Object other) { } ModelManager otherModelManager = (ModelManager) other; - return addressBook.equals(otherModelManager.addressBook) + return clientHub.equals(otherModelManager.clientHub) && userPrefs.equals(otherModelManager.userPrefs) - && filteredPersons.equals(otherModelManager.filteredPersons); + && originalPersons.equals(otherModelManager.originalPersons) + && filteredPersons.equals(otherModelManager.filteredPersons) + && sortedPersons.equals(otherModelManager.sortedPersons) + && originalReminders.equals(otherModelManager.originalReminders) + && filteredReminders.equals(otherModelManager.filteredReminders); } } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java deleted file mode 100644 index 6ddc2cd9a29..00000000000 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ /dev/null @@ -1,17 +0,0 @@ -package seedu.address.model; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; - -/** - * Unmodifiable view of an address book - */ -public interface ReadOnlyAddressBook { - - /** - * Returns an unmodifiable view of the persons list. - * This list will not contain any duplicate persons. - */ - ObservableList getPersonList(); - -} diff --git a/src/main/java/seedu/address/model/ReadOnlyClientHub.java b/src/main/java/seedu/address/model/ReadOnlyClientHub.java new file mode 100644 index 00000000000..9a306126070 --- /dev/null +++ b/src/main/java/seedu/address/model/ReadOnlyClientHub.java @@ -0,0 +1,19 @@ +package seedu.address.model; + +import javafx.collections.ObservableList; +import seedu.address.model.person.Person; +import seedu.address.model.reminder.Reminder; + +/** + * Unmodifiable view of an address book + */ +public interface ReadOnlyClientHub { + + /** + * Returns an unmodifiable view of the persons list and reminder list. + * person list will not contain any duplicate persons. + */ + ObservableList getPersonList(); + ObservableList getReminderList(); + +} diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java index befd58a4c73..1cff694b5f7 100644 --- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java +++ b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java @@ -11,6 +11,6 @@ public interface ReadOnlyUserPrefs { GuiSettings getGuiSettings(); - Path getAddressBookFilePath(); + Path getClientHubFilePath(); } diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 6be655fb4c7..e722de18623 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -14,7 +14,7 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path clientHubFilePath = Paths.get("data" , "clienthub.json"); /** * Creates a {@code UserPrefs} with default values. @@ -35,7 +35,7 @@ public UserPrefs(ReadOnlyUserPrefs userPrefs) { public void resetData(ReadOnlyUserPrefs newUserPrefs) { requireNonNull(newUserPrefs); setGuiSettings(newUserPrefs.getGuiSettings()); - setAddressBookFilePath(newUserPrefs.getAddressBookFilePath()); + setClientHubFilePath(newUserPrefs.getClientHubFilePath()); } public GuiSettings getGuiSettings() { @@ -47,13 +47,13 @@ public void setGuiSettings(GuiSettings guiSettings) { this.guiSettings = guiSettings; } - public Path getAddressBookFilePath() { - return addressBookFilePath; + public Path getClientHubFilePath() { + return clientHubFilePath; } - public void setAddressBookFilePath(Path addressBookFilePath) { - requireNonNull(addressBookFilePath); - this.addressBookFilePath = addressBookFilePath; + public void setClientHubFilePath(Path clientHubFilePath) { + requireNonNull(clientHubFilePath); + this.clientHubFilePath = clientHubFilePath; } @Override @@ -69,19 +69,19 @@ public boolean equals(Object other) { UserPrefs otherUserPrefs = (UserPrefs) other; return guiSettings.equals(otherUserPrefs.guiSettings) - && addressBookFilePath.equals(otherUserPrefs.addressBookFilePath); + && clientHubFilePath.equals(otherUserPrefs.clientHubFilePath); } @Override public int hashCode() { - return Objects.hash(guiSettings, addressBookFilePath); + return Objects.hash(guiSettings, clientHubFilePath); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Gui Settings : " + guiSettings); - sb.append("\nLocal data file location : " + addressBookFilePath); + sb.append("\nLocal data file location : " + clientHubFilePath); return sb.toString(); } diff --git a/src/main/java/seedu/address/model/clienttype/ClientType.java b/src/main/java/seedu/address/model/clienttype/ClientType.java new file mode 100644 index 00000000000..4aaf2e49f29 --- /dev/null +++ b/src/main/java/seedu/address/model/clienttype/ClientType.java @@ -0,0 +1,63 @@ +package seedu.address.model.clienttype; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a ClientType in the address book. + * Guarantees: immutable; name is valid as declared in {@link #isValidClientTypeName(String)} + */ +public class ClientType { + + public static final String MESSAGE_CONSTRAINTS = "ClientTypes names should be " + + "alphanumeric and only up to 30 characters long(space inclusive)"; + public static final String VALIDATION_REGEX = "[\\p{Alnum} ]{1,30}"; + + public final String clientTypeName; + + /** + * Constructs a {@code ClientType}. + * + * @param clientTypeName A valid client type name. + */ + public ClientType(String clientTypeName) { + requireNonNull(clientTypeName); + checkArgument(isValidClientTypeName(clientTypeName), MESSAGE_CONSTRAINTS); + this.clientTypeName = clientTypeName; + } + + /** + * Returns true if a given string is a valid client type name. + */ + public static boolean isValidClientTypeName(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ClientType)) { + return false; + } + + ClientType otherClientType = (ClientType) other; + return clientTypeName.equals(otherClientType.clientTypeName); + } + + @Override + public int hashCode() { + return clientTypeName.hashCode(); + } + + /** + * Format state as text for viewing. + */ + public String toString() { + return '[' + clientTypeName + ']'; + } + +} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java index 469a2cc9a1e..6d905244f4c 100644 --- a/src/main/java/seedu/address/model/person/Address.java +++ b/src/main/java/seedu/address/model/person/Address.java @@ -9,14 +9,14 @@ */ public class Address { - public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, and it should not be blank"; + public static final String MESSAGE_CONSTRAINTS = "Addresses can only take in letters, numbers or " + + "the following characters: ,#-():; " + + "\nIt should not be blank."; /* - * The first character of the address must not be a whitespace, - * otherwise " " (a blank string) becomes a valid input. + * Address should only contain ",#-():;", letters or alphabets. */ - public static final String VALIDATION_REGEX = "[^\\s].*"; - + public static final String VALIDATION_REGEX = "^(?=.*\\S)[a-zA-Z0-9#\\-(),:;\\s]+$"; public final String value; /** diff --git a/src/main/java/seedu/address/model/person/AddressContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/AddressContainsKeywordsPredicate.java new file mode 100644 index 00000000000..74dc30a0618 --- /dev/null +++ b/src/main/java/seedu/address/model/person/AddressContainsKeywordsPredicate.java @@ -0,0 +1,47 @@ +package seedu.address.model.person; + +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; + +/** + * Tests that a {@code Person}'s {@code Address} matches any of the keywords given. + */ +public class AddressContainsKeywordsPredicate implements Predicate { + + private final String keywords; + + public AddressContainsKeywordsPredicate(String keywords) { + this.keywords = keywords.trim(); + } + + @Override + public boolean test(Person person) { + if (keywords.isEmpty()) { + return false; + } + // Check if any of the keywords match any part of the address + return person.getAddress().value.toLowerCase().contains(keywords.toLowerCase()); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AddressContainsKeywordsPredicate)) { + return false; + } + + AddressContainsKeywordsPredicate otherAddressContainsKeywordsPredicate = + (AddressContainsKeywordsPredicate) other; + return keywords.equals(otherAddressContainsKeywordsPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/person/ClientTypeContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/ClientTypeContainsKeywordsPredicate.java new file mode 100644 index 00000000000..76a61597eba --- /dev/null +++ b/src/main/java/seedu/address/model/person/ClientTypeContainsKeywordsPredicate.java @@ -0,0 +1,78 @@ +package seedu.address.model.person; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.clienttype.ClientType; + +/** + * Tests that a {@code Person}'s {@code Client_Type} matches any of the keywords given. + */ +public class ClientTypeContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public ClientTypeContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + Set personClientTypes = person.getClientTypes(); + + // If the client type list is empty, return false -> no client type to match (Invalid Client Type) + if (this.keywords.isEmpty()) { + return false; + } + + // Try matching the combined phrase first + String combinedPhrase = String.join(" ", keywords).toLowerCase(); + if (personClientTypes.stream().anyMatch(clientType -> + clientType.clientTypeName.toLowerCase().contains(combinedPhrase))) { + return true; + } + + // Split each client type into individual words and flatten into a single stream of words + Set clientTypeWords = personClientTypes.stream() + .map(clientType -> clientType.clientTypeName.toLowerCase().split("\\s+")) + .flatMap(Arrays::stream) + .collect(Collectors.toSet()); + + // Check if all keywords match at least one of the client type words + return keywords.stream() + .allMatch(keyword -> clientTypeWords.stream() + .anyMatch(word -> word.startsWith(keyword.toLowerCase()))); + + + + // // If combined phrase does not match, try matching individual keywords + // return keywords.stream() + // .allMatch(keyword -> personClientTypes.stream().anyMatch(clientType -> + // clientType.clientTypeName.toLowerCase().startsWith(keyword.toLowerCase())) + // ); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ClientTypeContainsKeywordsPredicate)) { + return false; + } + + ClientTypeContainsKeywordsPredicate otherNameContainsKeywordsPredicate = + (ClientTypeContainsKeywordsPredicate) other; + return keywords.equals(otherNameContainsKeywordsPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/person/Description.java b/src/main/java/seedu/address/model/person/Description.java new file mode 100644 index 00000000000..1a5e5c94bb5 --- /dev/null +++ b/src/main/java/seedu/address/model/person/Description.java @@ -0,0 +1,66 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Description in the system. + * Guarantees: immutable; is valid as declared in {@link #VALIDATION_REGEX} + */ +public class Description { + public static final String MESSAGE_CONSTRAINTS = "Descriptions can take any values but not more than 500" + + "characters"; + + // Allow printable characters excluding control characters + public static final String VALIDATION_REGEX = "^[\\p{Print}&&[^\\p{Cntrl}]]*$"; + + public static final int MAX_LENGTH = 500; + public static final int MIN_LENGTH = 1; + + public final String description; + + /** + * Constructs a {@code Description}. + * + * @param description A valid description. + */ + public Description(String description) { + requireNonNull(description); + checkArgument(isValidDescription(description), MESSAGE_CONSTRAINTS); + this.description = description; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Description)) { + return false; + } + + Description otherDescription = (Description) other; + return description.equals(otherDescription.description); + } + + @Override + public int hashCode() { + return description.hashCode(); + } + + /** + * Format state as text for viewing. + */ + public String toString() { + return '[' + description + ']'; + } + + /** + * Returns true if a given string is a valid description. + */ + public static boolean isValidDescription(String test) { + return test.matches(VALIDATION_REGEX) + && test.trim().length() <= MAX_LENGTH && test.trim().length() >= MIN_LENGTH; + } +} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java index 173f15b9b00..16688871eaf 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/person/Name.java @@ -10,13 +10,15 @@ public class Name { public static final String MESSAGE_CONSTRAINTS = - "Names should only contain alphanumeric characters and spaces, and it should not be blank"; + "Names should only contain letters, space, parenthesis and slash, and it should not be blank. Names should " + + "start with a letter and end with a letter or closing bracket."; /* * 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-zA-Z()/\\s]*[a-zA-Z)]$"; + public final String fullName; @@ -28,7 +30,18 @@ public class Name { public Name(String name) { requireNonNull(name); checkArgument(isValidName(name), MESSAGE_CONSTRAINTS); - fullName = name; + + String[] words = name.split(" "); + String result = ""; + + for (String word : words) { + if (word.length() > 0) { + result += Character.toUpperCase(word.charAt(0)) + word.substring(1).toLowerCase() + " "; + } + } + + // Remove the extra trailing space and return the result + fullName = result.trim(); } /** @@ -56,7 +69,11 @@ public boolean equals(Object other) { } Name otherName = (Name) other; - return fullName.equals(otherName.fullName); + + // Compare the full name of the person + // John Doe will match john doe + // To prevent duplicate names from getting added. + return fullName.equalsIgnoreCase(otherName.fullName); } @Override diff --git a/src/main/java/seedu/address/model/person/NameComparator.java b/src/main/java/seedu/address/model/person/NameComparator.java new file mode 100644 index 00000000000..8e4076216bc --- /dev/null +++ b/src/main/java/seedu/address/model/person/NameComparator.java @@ -0,0 +1,38 @@ +package seedu.address.model.person; + +import java.util.Comparator; + +/** + * Compares that a {@code Person}'s {@code Name} is bigger than the other. + */ +public class NameComparator implements Comparator { + public NameComparator() {} + + @Override + public int compare(Person person1, Person person2) { + int comparison = person1.getName().fullName.compareTo(person2.getName().fullName); + + assert comparison != 0; // ensures no repeats of person + + return comparison; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof NameComparator)) { + return false; + } + + return true; + } + + @Override + public String toString() { + return getClass().getName(); + } +} diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsDeletePredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsDeletePredicate.java new file mode 100644 index 00000000000..d7185a9fb52 --- /dev/null +++ b/src/main/java/seedu/address/model/person/NameContainsKeywordsDeletePredicate.java @@ -0,0 +1,107 @@ +package seedu.address.model.person; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; + +/** + * Tests that a {@code Person}'s name matches any of the specified keywords. + */ +public class NameContainsKeywordsDeletePredicate implements Predicate { + private final List keywords; + + /** + * Constructs a {@code NameContainsKeywordsDeletePredicate} with the specified keywords. + * + * @param keywords A list of keywords to match against the {@code Person}'s name. + */ + public NameContainsKeywordsDeletePredicate(List keywords) { + this.keywords = keywords; + } + + /** + * Evaluates this predicate on the given argument. + * + * @param person The person to test. + * @return {@code true} if the person's name matches the predicate's criteria, otherwise {@code false}. + * Returns {@code true} if all keywords are found in the person's name, or if the last keyword + * ends with a "/", indicating an exact match. + */ + @Override + public boolean test(Person person) { + if (keywords.isEmpty()) { + return false; + } else if (keywords.get(keywords.size() - 1).contains("$")) { + return isExact(person); + } else { + String[] nameParts = person.getName().fullName.split("\\s+"); + // Create a list to track which name parts have been matched + List remainingParts = new ArrayList<>(Arrays.asList(nameParts)); + + for (String keyword : keywords) { + boolean foundMatch = false; + // Try to match this keyword with any remaining (unmatched) name part + for (int i = remainingParts.size() - 1; i >= 0; i--) { + if (remainingParts.get(i).toLowerCase().startsWith(keyword.toLowerCase())) { + remainingParts.remove(i); + foundMatch = true; + break; + } + } + // If any keyword doesn't find a match, return false + if (!foundMatch) { + return false; + } + } + return true; + } + } + + /** + * Checks if the person's name is an exact match with the concatenated keywords. + * + * @param person The person to compare against the concatenated keywords. + * @return {@code true} if the concatenated keywords, without the trailing "/", match the person's full name. + */ + public boolean isExact(Person person) { + String fullname = String.join("", keywords).toLowerCase().split("\\$")[0].trim(); + String personName = person.getName().fullName.trim().toLowerCase().replace(" ", ""); + return fullname.equals(personName); + } + + /** + * Checks if this predicate is equal to another object. + * + * @param other The other object to compare. + * @return {@code true} if the other object is an instance of {@code NameContainsKeywordsDeletePredicate} + * and has the same keywords, otherwise {@code false}. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof NameContainsKeywordsDeletePredicate)) { + return false; + } + + NameContainsKeywordsDeletePredicate otherNameContainsKeywordsDeletePredicate = + (NameContainsKeywordsDeletePredicate) other; + return keywords.equals(otherNameContainsKeywordsDeletePredicate.keywords); + } + + /** + * Returns a string representation of this predicate. + * + * @return A string that includes the list of keywords. + */ + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java index 62d19be2977..d8e5fb735a2 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java @@ -1,9 +1,10 @@ package seedu.address.model.person; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.function.Predicate; -import seedu.address.commons.util.StringUtil; import seedu.address.commons.util.ToStringBuilder; /** @@ -18,8 +19,45 @@ public NameContainsKeywordsPredicate(List keywords) { @Override public boolean test(Person person) { - return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + if (keywords.isEmpty()) { + return false; + } else if (keywords.get(keywords.size() - 1).contains("$")) { + return isExact(person); + } else { + String[] nameParts = person.getName().fullName.split("\\s+"); + // Create a list to track which name parts have been matched + List remainingParts = new ArrayList<>(Arrays.asList(nameParts)); + + for (String keyword : keywords) { + boolean foundMatch = false; + // Try to match this keyword with any remaining (unmatched) name part + for (int i = remainingParts.size() - 1; i >= 0; i--) { + if (remainingParts.get(i).toLowerCase().startsWith(keyword.toLowerCase())) { + remainingParts.remove(i); + foundMatch = true; + break; + } + } + // If any keyword doesn't find a match, return false + if (!foundMatch) { + return false; + } + } + return true; + } + } + + + /** + * Checks if the person's name is an exact match with the concatenated keywords. + * + * @param person The person to compare against the concatenated keywords. + * @return {@code true} if the concatenated keywords, match the person's full name. + */ + public boolean isExact(Person person) { + String fullname = String.join("", keywords).toLowerCase().split("\\$")[0].trim(); + String personName = person.getName().fullName.trim().toLowerCase().replace(" ", ""); + return fullname.equals(personName); } @Override diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index abe8c46b535..64226b9fc51 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -8,7 +8,8 @@ import java.util.Set; import seedu.address.commons.util.ToStringBuilder; -import seedu.address.model.tag.Tag; +import seedu.address.model.clienttype.ClientType; +import seedu.address.model.reminder.Reminder; /** * Represents a Person in the address book. @@ -20,21 +21,26 @@ public class Person { private final Name name; private final Phone phone; private final Email email; + private final Description description; // Data fields private final Address address; - private final Set tags = new HashSet<>(); + private final Set clientTypes = new HashSet<>(); + private final Set reminders = new HashSet<>(); /** * Every field must be present and not null. */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); + public Person(Name name, Phone phone, Email email, Address address, + Set clientTypes, Description description, Set reminders) { + requireAllNonNull(name, phone, email, address, clientTypes, description); this.name = name; this.phone = phone; this.email = email; this.address = address; - this.tags.addAll(tags); + this.clientTypes.addAll(clientTypes); + this.description = description; + this.reminders.addAll(reminders); } public Name getName() { @@ -53,12 +59,23 @@ public Address getAddress() { return address; } + public Description getDescription() { + return description; + } + + public Set getReminders() { + return Collections.unmodifiableSet(reminders); + } + /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * Returns an immutable client type set, which throws {@code UnsupportedOperationException} * if modification is attempted. */ - public Set getTags() { - return Collections.unmodifiableSet(tags); + public Set getClientTypes() { + return Collections.unmodifiableSet(clientTypes); + } + public void addReminder(Reminder reminder) { + reminders.add(reminder); } /** @@ -73,6 +90,9 @@ public boolean isSamePerson(Person otherPerson) { return otherPerson != null && otherPerson.getName().equals(getName()); } + public void deleteReminder(Reminder reminder) { + reminders.remove(reminder); + } /** * Returns true if both persons have the same identity and data fields. @@ -94,13 +114,15 @@ public boolean equals(Object other) { && phone.equals(otherPerson.phone) && email.equals(otherPerson.email) && address.equals(otherPerson.address) - && tags.equals(otherPerson.tags); + && clientTypes.equals(otherPerson.clientTypes) + && description.equals(otherPerson.description) + && reminders.equals(otherPerson.reminders); } @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, address, clientTypes, description, reminders); } @Override @@ -110,8 +132,9 @@ public String toString() { .add("phone", phone) .add("email", email) .add("address", address) - .add("tags", tags) + .add("clientTypes", clientTypes) + .add("description", description) + .add("reminders", reminders) .toString(); } - } diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java index d733f63d739..cedf9b23989 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/address/model/person/Phone.java @@ -11,8 +11,8 @@ public class Phone { public static final String MESSAGE_CONSTRAINTS = - "Phone numbers should only contain numbers, and it should be at least 3 digits long"; - public static final String VALIDATION_REGEX = "\\d{3,}"; + "Phone numbers should only contain numbers, and it should be 8 digits long"; + public static final String VALIDATION_REGEX = "\\d{8}"; public final String value; /** diff --git a/src/main/java/seedu/address/model/person/PhoneBeginsWithKeywordPredicate.java b/src/main/java/seedu/address/model/person/PhoneBeginsWithKeywordPredicate.java new file mode 100644 index 00000000000..b9b2dcbb034 --- /dev/null +++ b/src/main/java/seedu/address/model/person/PhoneBeginsWithKeywordPredicate.java @@ -0,0 +1,44 @@ +package seedu.address.model.person; + +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; + +/** + * Tests that a {@code Person}'s {@code Phone} matches any of the keywords given. + */ +public class PhoneBeginsWithKeywordPredicate implements Predicate { + private final String keyword; + + public PhoneBeginsWithKeywordPredicate(String keyword) { + this.keyword = keyword; + } + + @Override + public boolean test(Person person) { + if (keyword.isEmpty()) { + return false; + } + return person.getPhone().value.startsWith(keyword); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof PhoneBeginsWithKeywordPredicate)) { + return false; + } + + PhoneBeginsWithKeywordPredicate otherPhoneBeginsWithKeywordPredicate = (PhoneBeginsWithKeywordPredicate) other; + return keyword.equals(otherPhoneBeginsWithKeywordPredicate.keyword); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keyword", keyword).toString(); + } +} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java index cc0a68d79f9..a8bacf7b94c 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/address/model/person/UniquePersonList.java @@ -5,6 +5,7 @@ import java.util.Iterator; import java.util.List; +import java.util.stream.Stream; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -27,6 +28,9 @@ public class UniquePersonList implements Iterable { private final ObservableList internalList = FXCollections.observableArrayList(); private final ObservableList internalUnmodifiableList = FXCollections.unmodifiableObservableList(internalList); + public Stream stream() { + return internalList.stream(); + } /** * Returns true if the list contains an equivalent person as the given argument. diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java index fa764426ca7..26ca466245c 100644 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java @@ -3,4 +3,8 @@ /** * Signals that the operation is unable to find the specified person. */ -public class PersonNotFoundException extends RuntimeException {} +public class PersonNotFoundException extends RuntimeException { + public PersonNotFoundException() { + super("Person not found!"); + } +} diff --git a/src/main/java/seedu/address/model/reminder/Reminder.java b/src/main/java/seedu/address/model/reminder/Reminder.java new file mode 100644 index 00000000000..b5d7867eeb4 --- /dev/null +++ b/src/main/java/seedu/address/model/reminder/Reminder.java @@ -0,0 +1,108 @@ +package seedu.address.model.reminder; +import java.time.LocalDateTime; +import java.util.Objects; + +import seedu.address.commons.util.ToStringBuilder; + +/** + * Represents a Reminder associated with a person in the address book. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Reminder { + private final String personName; + private final LocalDateTime dateTime; + private final ReminderDescription description; + + /** + * Creates a Reminder with the specified person, date and time, and description. + * + * @param person the person associated with the reminder + * @param dateTime the date and time of the reminder + * @param description the description of the reminder + */ + public Reminder(String person, LocalDateTime dateTime, ReminderDescription description) { + this.personName = Objects.requireNonNull(person, "Person name cannot be null"); + this.dateTime = Objects.requireNonNull(dateTime, "DateTime cannot be null"); + this.description = Objects.requireNonNull(description, "Description cannot be null"); + } + + public String getPersonName() { + return personName; + } + + public LocalDateTime getDateTime() { + return dateTime; + } + + public ReminderDescription getDescription() { + return description; + } + + /** + * Returns true if both reminders are associated with the same person, date and time, + * and have the same description. This defines a stronger notion of equality between two reminders. + * + * @param other the other object to compare to + * @return true if both reminders are equal, false otherwise + */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (!(other instanceof Reminder)) { + return false; + } + + Reminder otherReminder = (Reminder) other; + return personName.equals(otherReminder.personName) + && dateTime.equals(otherReminder.dateTime) + && description.equals(otherReminder.description); + } + + /** + * Checks if this reminder is the same as another reminder. + * Two reminders are considered the same if they have identical descriptions and times. + * + * @param otherReminder The other reminder to compare with. + * @return True if the reminders are the same, false otherwise. + */ + public boolean isSameReminder(Reminder otherReminder) { + if (otherReminder == this) { + return true; + } + + return otherReminder != null + && otherReminder.getDescription().equals(getDescription()) + && otherReminder.getDateTime().equals(getDateTime()); + } + @Override + public int hashCode() { + return Objects.hash(personName, dateTime, description); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("name", personName) + .add("Date and time", this.getFormattedDateTime()) + .add("description", description) + .toString(); + } + + public Reminder getReminderWithFullName(String fullName) { + return new Reminder(fullName, this.dateTime, this.description); + } + + /** + * Returns the formatted date and time string. + * @return formatted date and time string + */ + public String getFormattedDateTime() { + String dateString = this.dateTime.toLocalDate().toString(); + String timeString = this.dateTime.toLocalTime().toString(); + return dateString + " " + timeString; + } + +} diff --git a/src/main/java/seedu/address/model/reminder/ReminderDescription.java b/src/main/java/seedu/address/model/reminder/ReminderDescription.java new file mode 100644 index 00000000000..6482a2289fe --- /dev/null +++ b/src/main/java/seedu/address/model/reminder/ReminderDescription.java @@ -0,0 +1,66 @@ +package seedu.address.model.reminder; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Description in the system. + * Guarantees: immutable; is valid as declared in {@link #VALIDATION_REGEX} + */ +public class ReminderDescription { + public static final String MESSAGE_CONSTRAINTS = "Reminder description cannot be empty or be more than " + + "300 characters"; + + // Allow printable characters excluding control characters + public static final String VALIDATION_REGEX = "^[\\p{Print}&&[^\\p{Cntrl}]]*$"; + + public static final int MAX_LENGTH = 300; + public static final int MIN_LENGTH = 1; + + public final String description; + + /** + * Constructs a {@code Description}. + * + * @param description A valid description. + */ + public ReminderDescription(String description) { + requireNonNull(description); + checkArgument(isValidReminderDescription(description), MESSAGE_CONSTRAINTS); + this.description = description; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof ReminderDescription)) { + return false; + } + + ReminderDescription otherDescription = (ReminderDescription) other; + return description.equals(otherDescription.description); + } + + @Override + public int hashCode() { + return description.hashCode(); + } + + /** + * Format state as text for viewing. + */ + public String toString() { + return '[' + description + ']'; + } + + /** + * Returns true if a given string is a valid description. + */ + public static boolean isValidReminderDescription(String test) { + return test.matches(VALIDATION_REGEX) + && test.trim().length() <= MAX_LENGTH && test.trim().length() >= MIN_LENGTH; + } +} diff --git a/src/main/java/seedu/address/model/reminder/ReminderList.java b/src/main/java/seedu/address/model/reminder/ReminderList.java new file mode 100644 index 00000000000..72b04bbe650 --- /dev/null +++ b/src/main/java/seedu/address/model/reminder/ReminderList.java @@ -0,0 +1,148 @@ +package seedu.address.model.reminder; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +/** + * A list of reminders that does not allow nulls. + * Supports a minimal set of list operations. + */ +public class ReminderList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = FXCollections + .unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent reminder as the given argument. + * + * @param toCheck The reminder to check. + * @return True if the reminder is found in the list, false otherwise. + */ + public boolean contains(Reminder toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameReminder); + } + + /** + * Adds a reminder to the list. + * + * @param toAdd The reminder to add. Must not already exist in the list. + */ + public void add(Reminder toAdd) { + requireNonNull(toAdd); + internalList.add(toAdd); + } + + /** + * Replaces the given reminder {@code target} in the list with {@code editedReminder}. + * + * @param target The reminder to be replaced. Must exist in the list. + * @param editedReminder The reminder to replace {@code target} with. + * @throws ReminderNotFoundException If {@code target} is not found in the list. + */ + public void setReminder(Reminder target, Reminder editedReminder) { + requireAllNonNull(target, editedReminder); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new ReminderNotFoundException(); + } + + internalList.set(index, editedReminder); + } + + /** + * Removes the specified reminder from the list. + * + * @param toRemove The reminder to remove. Must exist in the list. + * @throws ReminderNotFoundException If the reminder is not found in the list. + */ + public void remove(Reminder toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new ReminderNotFoundException(); + } + } + + /** + * Replaces the contents of this list with {@code replacement}. + * + * @param replacement The list to replace the current list with. + */ + public void setReminders(ReminderList replacement) { + requireNonNull(replacement); + internalList.setAll(replacement.internalList); + } + + /** + * Replaces the contents of this list with {@code reminders}. + * + * @param reminders The list of reminders to set. + */ + public void setReminders(List reminders) { + requireAllNonNull(reminders); + internalList.setAll(reminders); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + * + * @return The unmodifiable observable list of reminders. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + /** + * Checks if this list is equal to another object. + * + * @param other The object to compare with. + * @return True if the lists are equal, false otherwise. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof ReminderList)) { + return false; + } + + ReminderList otherReminderList = (ReminderList) other; + return internalList.equals(otherReminderList.internalList); + } + + /** + * Returns the hash code of the list. + * + * @return The hash code of the internal list. + */ + @Override + public int hashCode() { + return internalList.hashCode(); + } + + /** + * Returns a string representation of the reminder list. + * + * @return A string representing the reminder list. + */ + @Override + public String toString() { + return internalList.toString(); + } + +} diff --git a/src/main/java/seedu/address/model/reminder/ReminderNotFoundException.java b/src/main/java/seedu/address/model/reminder/ReminderNotFoundException.java new file mode 100644 index 00000000000..26b28dabd12 --- /dev/null +++ b/src/main/java/seedu/address/model/reminder/ReminderNotFoundException.java @@ -0,0 +1,13 @@ +package seedu.address.model.reminder; + +/** + * Signals that the specified reminder could not be found. + */ +public class ReminderNotFoundException extends RuntimeException { + /** + * Constructs a {@code ReminderNotFoundException} with a default error message. + */ + public ReminderNotFoundException() { + super("Reminder not found!"); + } +} 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 f1a0d4e233b..00000000000 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ /dev/null @@ -1,62 +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) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof Tag)) { - return false; - } - - Tag otherTag = (Tag) other; - return tagName.equals(otherTag.tagName); - } - - @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/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index 1806da4facf..d6eeb6ca815 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -1,60 +1,78 @@ package seedu.address.model.util; import java.util.Arrays; +import java.util.Collections; import java.util.Set; import java.util.stream.Collectors; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ClientHub; +import seedu.address.model.ReadOnlyClientHub; +import seedu.address.model.clienttype.ClientType; import seedu.address.model.person.Address; +import seedu.address.model.person.Description; 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.reminder.Reminder; /** - * Contains utility methods for populating {@code AddressBook} with sample data. + * Contains utility methods for populating {@code ClientHub} with sample data. */ public class SampleDataUtil { public static Person[] getSamplePersons() { return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), + new Person(new Name("Ahmad Syuaib"), new Phone("87438807"), new Email("ahmadsyuaib@example.com"), new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), + getClientTypeSet("Investment"), new Description("Only free on weekends"), Collections.EMPTY_SET), + new Person(new Name("Jeremy Sim"), new Phone("99272758"), new Email("jeremysim@example.com"), new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), + getClientTypeSet("Investment", "Healthcare"), new Description("Likes to play basketball"), + Collections.EMPTY_SET), + new Person(new Name("Liu Rui"), new Phone("93210283"), new Email("liurui@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"), + getClientTypeSet("Insurance Plan", "Savings"), new Description("Plans to retire early"), + Collections.EMPTY_SET), + new Person(new Name("Rubin Lin"), new Phone("91031282"), new Email("rubinlin@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"), + getClientTypeSet("Healthcare", "Savings"), new Description("Started a podcast"), + Collections.EMPTY_SET), + new Person(new Name("Harith Nurhisham"), new Phone("92492021"), new Email("harithnurhisham@example.com"), new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), + getClientTypeSet("Insurance Plan", "Investment"), new Description("Does not like to travel " + + "out of the east"), Collections.EMPTY_SET), 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")) + getClientTypeSet("Investment"), new Description("Recently married"), Collections.EMPTY_SET), + new Person(new Name("Sarah Lee"), new Phone("89732145"), new Email("sarahlee@example.com"), + new Address("Blk 38 Boon Lay Street 43, #32-03"), + getClientTypeSet("Healthcare", "Insurance Plan"), + new Description("Takes care of old parents"), Collections.EMPTY_SET), }; } - public static ReadOnlyAddressBook getSampleAddressBook() { - AddressBook sampleAb = new AddressBook(); + public static ReadOnlyClientHub getSampleClientHub() { + ClientHub sampleCh = new ClientHub(); for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); + sampleCh.addPerson(samplePerson); } - return sampleAb; + return sampleCh; } /** - * Returns a tag set containing the list of strings given. + * Returns a client type set containing the list of strings given. */ - public static Set getTagSet(String... strings) { + public static Set getClientTypeSet(String... strings) { return Arrays.stream(strings) - .map(Tag::new) + .map(ClientType::new) .collect(Collectors.toSet()); } + /** + * Returns a client type set containing the list of reminders given. + */ + public static Set getReminderSet(Reminder... reminders) { + return Arrays.stream(reminders) + .collect(Collectors.toSet()); + } } diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java deleted file mode 100644 index f2e015105ae..00000000000 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ /dev/null @@ -1,45 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - -import seedu.address.commons.exceptions.DataLoadingException; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * Represents a storage for {@link seedu.address.model.AddressBook}. - */ -public interface AddressBookStorage { - - /** - * Returns the file path of the data file. - */ - Path getAddressBookFilePath(); - - /** - * Returns AddressBook data as a {@link ReadOnlyAddressBook}. - * Returns {@code Optional.empty()} if storage file is not found. - * - * @throws DataLoadingException if loading the data from storage failed. - */ - Optional readAddressBook() throws DataLoadingException; - - /** - * @see #getAddressBookFilePath() - */ - Optional readAddressBook(Path filePath) throws DataLoadingException; - - /** - * Saves the given {@link ReadOnlyAddressBook} to the storage. - * @param addressBook cannot be null. - * @throws IOException if there was any problem writing to the file. - */ - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - - /** - * @see #saveAddressBook(ReadOnlyAddressBook) - */ - void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/ClientHubStorage.java b/src/main/java/seedu/address/storage/ClientHubStorage.java new file mode 100644 index 00000000000..385dccca1fb --- /dev/null +++ b/src/main/java/seedu/address/storage/ClientHubStorage.java @@ -0,0 +1,46 @@ +package seedu.address.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.address.commons.exceptions.DataLoadingException; +import seedu.address.model.ClientHub; +import seedu.address.model.ReadOnlyClientHub; + +/** + * Represents a storage for {@link ClientHub}. + */ +public interface ClientHubStorage { + + /** + * Returns the file path of the data file. + */ + Path getClientHubFilePath(); + + /** + * Returns ClientHub data as a {@link ReadOnlyClientHub}. + * Returns {@code Optional.empty()} if storage file is not found. + * + * @throws DataLoadingException if loading the data from storage failed. + */ + Optional readClientHub() throws DataLoadingException; + + /** + * @see #getClientHubFilePath() + */ + Optional readClientHub(Path filePath) throws DataLoadingException; + + /** + * Saves the given {@link ReadOnlyClientHub} to the storage. + * @param clientHub cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveClientHub(ReadOnlyClientHub clientHub) throws IOException; + + /** + * @see #saveClientHub(ReadOnlyClientHub) (ReadOnlyClientHub) + */ + void saveClientHub(ReadOnlyClientHub clientHub, Path filePath) throws IOException; + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedClientType.java b/src/main/java/seedu/address/storage/JsonAdaptedClientType.java new file mode 100644 index 00000000000..8c3de694013 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedClientType.java @@ -0,0 +1,48 @@ +package seedu.address.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.clienttype.ClientType; + +/** + * Jackson-friendly version of {@link ClientType}. + */ +class JsonAdaptedClientType { + + private final String clientTypeName; + + /** + * Constructs a {@code JsonAdaptedclientType} with the given {@code clientTypeName}. + */ + @JsonCreator + public JsonAdaptedClientType(String clientTypeName) { + this.clientTypeName = clientTypeName; + } + + /** + * Converts a given {@code clientType} into this class for Jackson use. + */ + public JsonAdaptedClientType(ClientType source) { + clientTypeName = source.clientTypeName; + } + + @JsonValue + public String getClientTypeName() { + return clientTypeName; + } + + /** + * Converts this Jackson-friendly adapted client type object into the model's {@code clientType} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted client type. + */ + public ClientType toModelType() throws IllegalValueException { + if (!ClientType.isValidClientTypeName(clientTypeName)) { + throw new IllegalValueException(ClientType.MESSAGE_CONSTRAINTS); + } + return new ClientType(clientTypeName); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java index bd1ca0f56c8..b127333cf95 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java @@ -10,12 +10,14 @@ import com.fasterxml.jackson.annotation.JsonProperty; import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.clienttype.ClientType; import seedu.address.model.person.Address; +import seedu.address.model.person.Description; 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.reminder.Reminder; /** * Jackson-friendly version of {@link Person}. @@ -28,7 +30,9 @@ class JsonAdaptedPerson { private final String phone; private final String email; private final String address; - private final List tags = new ArrayList<>(); + private final List clientTypes = new ArrayList<>(); + private final String description; + private final List reminders = new ArrayList<>(); /** * Constructs a {@code JsonAdaptedPerson} with the given person details. @@ -36,13 +40,19 @@ class JsonAdaptedPerson { @JsonCreator public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tags") List tags) { + @JsonProperty("clientTypes") List clientTypes, + @JsonProperty("description") String description, + @JsonProperty("reminders") List reminders) { this.name = name; this.phone = phone; this.email = email; this.address = address; - if (tags != null) { - this.tags.addAll(tags); + if (clientTypes != null) { + this.clientTypes.addAll(clientTypes); + } + this.description = description; + if (reminders != null) { + this.reminders.addAll(reminders); } } @@ -54,8 +64,12 @@ public JsonAdaptedPerson(Person source) { phone = source.getPhone().value; email = source.getEmail().value; address = source.getAddress().value; - tags.addAll(source.getTags().stream() - .map(JsonAdaptedTag::new) + clientTypes.addAll(source.getClientTypes().stream() + .map(JsonAdaptedClientType::new) + .collect(Collectors.toList())); + description = source.getDescription().description; + reminders.addAll(source.getReminders().stream() + .map(JsonAdaptedReminder::new) .collect(Collectors.toList())); } @@ -65,9 +79,13 @@ public JsonAdaptedPerson(Person source) { * @throws IllegalValueException if there were any data constraints violated in the adapted person. */ public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); - for (JsonAdaptedTag tag : tags) { - personTags.add(tag.toModelType()); + final List personClientTypes = new ArrayList<>(); + for (JsonAdaptedClientType clientType : clientTypes) { + personClientTypes.add(clientType.toModelType()); + } + final List reminders = new ArrayList<>(); + for (JsonAdaptedReminder reminder : this.reminders) { + reminders.add(reminder.toModelType()); } if (name == null) { @@ -102,8 +120,23 @@ public Person toModelType() throws IllegalValueException { } final Address modelAddress = new Address(address); - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); + final Set modelClientTypes = new HashSet<>(personClientTypes); + + + if (description == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Description.class.getSimpleName())); + } + + if (!Description.isValidDescription(description)) { + throw new IllegalValueException(Description.MESSAGE_CONSTRAINTS); + } + + final Description modelDescription = new Description(description); + final Set modelReminder = new HashSet<>(reminders); + + return new Person(modelName, modelPhone, modelEmail, modelAddress, modelClientTypes, modelDescription, + modelReminder); } } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedReminder.java b/src/main/java/seedu/address/storage/JsonAdaptedReminder.java new file mode 100644 index 00000000000..a08ab857abb --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedReminder.java @@ -0,0 +1,72 @@ +package seedu.address.storage; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.reminder.Reminder; +import seedu.address.model.reminder.ReminderDescription; + +/** + * Jackson-friendly version of {@link Reminder}. + */ +class JsonAdaptedReminder { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Reminder's %s field is missing!"; + private static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm"; + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT); + private final String personName; + private final String dateTime; + private final String reminderDescription; + + /** + * Constructs a {@code JsonAdaptedReminder} with the given reminder details. + */ + @JsonCreator + public JsonAdaptedReminder(@JsonProperty("personName") String personName, @JsonProperty("dateTime") String dateTime, + @JsonProperty("reminderDescription") String reminderDescription) { + this.personName = personName; + this.dateTime = dateTime; + this.reminderDescription = reminderDescription; + } + + /** + * Converts a given {@code Reminder} into this class for Jackson use. + */ + public JsonAdaptedReminder(Reminder source) { + personName = source.getPersonName(); + dateTime = source.getDateTime().format(formatter); + reminderDescription = source.getDescription().description; + } + + /** + * Converts this Jackson-friendly adapted reminder object into the model's {@code Reminder} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted reminder. + */ + public Reminder toModelType() throws IllegalValueException { + if (dateTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "DateTime")); + } + + LocalDateTime modelDateTime; + try { + modelDateTime = LocalDateTime.parse(dateTime, formatter); + } catch (DateTimeParseException e) { + throw new IllegalValueException("DateTime must be in format: " + DATE_TIME_FORMAT); + } + + if (reminderDescription == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "ReminderDescription")); + } + if (!ReminderDescription.isValidReminderDescription(reminderDescription)) { + throw new IllegalValueException(ReminderDescription.MESSAGE_CONSTRAINTS); + } + + return new Reminder(personName, modelDateTime, new ReminderDescription(reminderDescription)); + } +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java deleted file mode 100644 index 0df22bdb754..00000000000 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ /dev/null @@ -1,48 +0,0 @@ -package seedu.address.storage; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.tag.Tag; - -/** - * Jackson-friendly version of {@link Tag}. - */ -class JsonAdaptedTag { - - private final String tagName; - - /** - * Constructs a {@code JsonAdaptedTag} with the given {@code tagName}. - */ - @JsonCreator - public JsonAdaptedTag(String tagName) { - this.tagName = tagName; - } - - /** - * Converts a given {@code Tag} into this class for Jackson use. - */ - public JsonAdaptedTag(Tag source) { - tagName = source.tagName; - } - - @JsonValue - public String getTagName() { - return tagName; - } - - /** - * Converts this Jackson-friendly adapted tag object into the model's {@code Tag} object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted tag. - */ - public Tag toModelType() throws IllegalValueException { - if (!Tag.isValidTagName(tagName)) { - throw new IllegalValueException(Tag.MESSAGE_CONSTRAINTS); - } - return new Tag(tagName); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonClientHubStorage.java similarity index 50% rename from src/main/java/seedu/address/storage/JsonAddressBookStorage.java rename to src/main/java/seedu/address/storage/JsonClientHubStorage.java index 41e06f264e1..9353146b478 100644 --- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java +++ b/src/main/java/seedu/address/storage/JsonClientHubStorage.java @@ -12,47 +12,47 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.commons.util.FileUtil; import seedu.address.commons.util.JsonUtil; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyClientHub; /** - * A class to access AddressBook data stored as a json file on the hard disk. + * A class to access ClientHub data stored as a json file on the hard disk. */ -public class JsonAddressBookStorage implements AddressBookStorage { +public class JsonClientHubStorage implements ClientHubStorage { - private static final Logger logger = LogsCenter.getLogger(JsonAddressBookStorage.class); + private static final Logger logger = LogsCenter.getLogger(JsonClientHubStorage.class); private Path filePath; - public JsonAddressBookStorage(Path filePath) { + public JsonClientHubStorage(Path filePath) { this.filePath = filePath; } - public Path getAddressBookFilePath() { + public Path getClientHubFilePath() { return filePath; } @Override - public Optional readAddressBook() throws DataLoadingException { - return readAddressBook(filePath); + public Optional readClientHub() throws DataLoadingException { + return readClientHub(filePath); } /** - * Similar to {@link #readAddressBook()}. + * Similar to {@link #readClientHub()}. * * @param filePath location of the data. Cannot be null. * @throws DataLoadingException if loading the data from storage failed. */ - public Optional readAddressBook(Path filePath) throws DataLoadingException { + public Optional readClientHub(Path filePath) throws DataLoadingException { requireNonNull(filePath); - Optional jsonAddressBook = JsonUtil.readJsonFile( - filePath, JsonSerializableAddressBook.class); - if (!jsonAddressBook.isPresent()) { + Optional jsonClientHub = JsonUtil.readJsonFile( + filePath, JsonSerializableClientHub.class); + if (!jsonClientHub.isPresent()) { return Optional.empty(); } try { - return Optional.of(jsonAddressBook.get().toModelType()); + return Optional.of(jsonClientHub.get().toModelType()); } catch (IllegalValueException ive) { logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); throw new DataLoadingException(ive); @@ -60,21 +60,21 @@ public Optional readAddressBook(Path filePath) throws DataL } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, filePath); + public void saveClientHub(ReadOnlyClientHub clientHub) throws IOException { + saveClientHub(clientHub, filePath); } /** - * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)}. + * Similar to {@link #saveClientHub(ReadOnlyClientHub)}. * * @param filePath location of the data. Cannot be null. */ - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - requireNonNull(addressBook); + public void saveClientHub(ReadOnlyClientHub clientHub, Path filePath) throws IOException { + requireNonNull(clientHub); requireNonNull(filePath); FileUtil.createIfMissing(filePath); - JsonUtil.saveJsonFile(new JsonSerializableAddressBook(addressBook), filePath); + JsonUtil.saveJsonFile(new JsonSerializableClientHub(clientHub), filePath); } } diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java deleted file mode 100644 index 5efd834091d..00000000000 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonRootName; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; - -/** - * An Immutable AddressBook that is serializable to JSON format. - */ -@JsonRootName(value = "addressbook") -class JsonSerializableAddressBook { - - public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; - - private final List persons = new ArrayList<>(); - - /** - * Constructs a {@code JsonSerializableAddressBook} with the given persons. - */ - @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { - this.persons.addAll(persons); - } - - /** - * 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())); - } - - /** - * Converts this address book into the model's {@code AddressBook} object. - * - * @throws IllegalValueException if there were any data constraints violated. - */ - public AddressBook toModelType() throws IllegalValueException { - AddressBook addressBook = new AddressBook(); - for (JsonAdaptedPerson jsonAdaptedPerson : persons) { - Person person = jsonAdaptedPerson.toModelType(); - if (addressBook.hasPerson(person)) { - throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); - } - addressBook.addPerson(person); - } - return addressBook; - } - -} diff --git a/src/main/java/seedu/address/storage/JsonSerializableClientHub.java b/src/main/java/seedu/address/storage/JsonSerializableClientHub.java new file mode 100644 index 00000000000..577bc16f9c5 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonSerializableClientHub.java @@ -0,0 +1,82 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.ClientHub; +import seedu.address.model.ReadOnlyClientHub; +import seedu.address.model.person.Person; +import seedu.address.model.reminder.Reminder; + +/** + * An Immutable ClientHub that is serializable to JSON format. + */ +@JsonRootName(value = "clienthub") +class JsonSerializableClientHub { + + public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; + public static final String MESSAGE_DUPLICATE_REMINDER = "Reminders list contains duplicate reminder(s)."; + + private final List persons = new ArrayList<>(); + private final List reminders = new ArrayList<>(); + + /** + * Constructs a {@code JsonSerializableClientHub} with the given persons and reminders. + */ + @JsonCreator + public JsonSerializableClientHub( + @JsonProperty("persons") List persons, + @JsonProperty("reminders") List reminders) { + if (persons != null) { + this.persons.addAll(persons); + } + if (reminders != null) { + this.reminders.addAll(reminders); + } + } + + /** + * Converts a given {@code ReadOnlyClientHub} into this class for Jackson use. + * + * @param source future changes to this will not affect the created {@code JsonSerializableClientHub}. + */ + public JsonSerializableClientHub(ReadOnlyClientHub source) { + persons.addAll(source.getPersonList().stream().map(JsonAdaptedPerson::new).collect(Collectors.toList())); + reminders.addAll(source.getReminderList().stream().map(JsonAdaptedReminder::new).collect(Collectors.toList())); + } + + /** + * Converts this client hub into the model's {@code ClientHub} object. + * + * @throws IllegalValueException if there were any data constraints violated. + */ + public ClientHub toModelType() throws IllegalValueException { + ClientHub clientHub = new ClientHub(); + + // Add persons to the ClientHub, checking for duplicates + for (JsonAdaptedPerson jsonAdaptedPerson : persons) { + Person person = jsonAdaptedPerson.toModelType(); + if (clientHub.hasPerson(person)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); + } + clientHub.addPerson(person); + } + + // Add reminders to the ClientHub, checking for duplicates + for (JsonAdaptedReminder jsonAdaptedReminder : reminders) { + Reminder reminder = jsonAdaptedReminder.toModelType(); + if (clientHub.hasReminder(reminder)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_REMINDER); + } + clientHub.addReminder(reminder); + } + + return clientHub; + } +} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java index 9fba0c7a1d6..45c898a3af6 100644 --- a/src/main/java/seedu/address/storage/Storage.java +++ b/src/main/java/seedu/address/storage/Storage.java @@ -5,14 +5,14 @@ import java.util.Optional; import seedu.address.commons.exceptions.DataLoadingException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyClientHub; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; /** * API of the Storage component */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { +public interface Storage extends ClientHubStorage, UserPrefsStorage { @Override Optional readUserPrefs() throws DataLoadingException; @@ -21,12 +21,12 @@ public interface Storage extends AddressBookStorage, UserPrefsStorage { void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException; @Override - Path getAddressBookFilePath(); + Path getClientHubFilePath(); @Override - Optional readAddressBook() throws DataLoadingException; + Optional readClientHub() throws DataLoadingException; @Override - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; + void saveClientHub(ReadOnlyClientHub clientHub) throws IOException; } diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java index 8b84a9024d5..3e5e4210222 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/seedu/address/storage/StorageManager.java @@ -7,24 +7,24 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.commons.exceptions.DataLoadingException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyClientHub; import seedu.address.model.ReadOnlyUserPrefs; import seedu.address.model.UserPrefs; /** - * Manages storage of AddressBook data in local storage. + * Manages storage of ClientHub data in local storage. */ public class StorageManager implements Storage { private static final Logger logger = LogsCenter.getLogger(StorageManager.class); - private AddressBookStorage addressBookStorage; + private ClientHubStorage clientHubStorage; private UserPrefsStorage userPrefsStorage; /** - * Creates a {@code StorageManager} with the given {@code AddressBookStorage} and {@code UserPrefStorage}. + * Creates a {@code StorageManager} with the given {@code ClientHubStorage} and {@code UserPrefStorage}. */ - public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { - this.addressBookStorage = addressBookStorage; + public StorageManager(ClientHubStorage clientHubStorage, UserPrefsStorage userPrefsStorage) { + this.clientHubStorage = clientHubStorage; this.userPrefsStorage = userPrefsStorage; } @@ -46,33 +46,33 @@ public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException { } - // ================ AddressBook methods ============================== + // ================ ClientHub methods ============================== @Override - public Path getAddressBookFilePath() { - return addressBookStorage.getAddressBookFilePath(); + public Path getClientHubFilePath() { + return clientHubStorage.getClientHubFilePath(); } @Override - public Optional readAddressBook() throws DataLoadingException { - return readAddressBook(addressBookStorage.getAddressBookFilePath()); + public Optional readClientHub() throws DataLoadingException { + return readClientHub(clientHubStorage.getClientHubFilePath()); } @Override - public Optional readAddressBook(Path filePath) throws DataLoadingException { + public Optional readClientHub(Path filePath) throws DataLoadingException { logger.fine("Attempting to read data from file: " + filePath); - return addressBookStorage.readAddressBook(filePath); + return clientHubStorage.readClientHub(filePath); } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath()); + public void saveClientHub(ReadOnlyClientHub clientHub) throws IOException { + saveClientHub(clientHub, clientHubStorage.getClientHubFilePath()); } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { + public void saveClientHub(ReadOnlyClientHub clientHub, Path filePath) throws IOException { logger.fine("Attempting to write to data file: " + filePath); - addressBookStorage.saveAddressBook(addressBook, filePath); + clientHubStorage.saveClientHub(clientHub, filePath); } } diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java index 9e75478664b..6dd771a5037 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/address/ui/CommandBox.java @@ -1,8 +1,12 @@ package seedu.address.ui; +import java.util.ArrayList; +import java.util.List; + import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.TextField; +import javafx.scene.input.KeyEvent; import javafx.scene.layout.Region; import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; @@ -18,6 +22,10 @@ public class CommandBox extends UiPart { private final CommandExecutor commandExecutor; + // Attributes for command history + private int commandHistoryIndex = -1; + private List commandHistory = new ArrayList<>(); + @FXML private TextField commandTextField; @@ -27,10 +35,54 @@ public class CommandBox extends UiPart { public CommandBox(CommandExecutor commandExecutor) { super(FXML); this.commandExecutor = commandExecutor; + + // Listens for key presses on the command box. + commandTextField.setOnKeyPressed(this::handleKeyPress); // calls #setStyleToDefault() whenever there is a change to the text of the command box. commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault()); } + private void handleKeyPress(KeyEvent keyEvent) { + switch (keyEvent.getCode()) { + case UP: + navigateToPreviousCommand(); + keyEvent.consume(); + break; + case DOWN: + navigateToNextCommand(); + keyEvent.consume(); + break; + default: + // do nothing + } + } + + private void navigateToPreviousCommand() { + if (commandHistoryIndex > 0) { + commandHistoryIndex--; + commandTextField.setText(commandHistory.get(commandHistoryIndex)); + commandTextField.positionCaret(commandTextField.getText().length()); + } else if (commandHistoryIndex == 0) { + commandTextField.setText(commandHistory.get(0)); + commandTextField.positionCaret(commandTextField.getText().length()); + } else { + commandTextField.clear(); + } + } + + private void navigateToNextCommand() { + if (commandHistoryIndex < commandHistory.size() - 1) { + commandHistoryIndex++; + commandTextField.setText(commandHistory.get(commandHistoryIndex)); + commandTextField.positionCaret(commandTextField.getText().length()); + } else if (commandHistoryIndex == commandHistory.size() - 1) { + commandHistoryIndex = commandHistory.size(); + commandTextField.clear(); + } else { + commandTextField.clear(); + } + } + /** * Handles the Enter button pressed event. */ @@ -40,12 +92,17 @@ private void handleCommandEntered() { if (commandText.equals("")) { return; } - try { commandExecutor.execute(commandText); commandTextField.setText(""); + } catch (CommandException | ParseException e) { setStyleToIndicateCommandFailure(); + + } finally { + // Update command history + commandHistory.add(commandText); + commandHistoryIndex = commandHistory.size(); } } diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 3f16b2fcf26..aea3eb0b8e0 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/address/ui/HelpWindow.java @@ -15,9 +15,50 @@ */ public class HelpWindow extends UiPart { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; - public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; + public static final String USERGUIDE_URL = "https://ay2425s1-cs2103t-f10-1.github.io/tp/UserGuide.html"; + public static final String USERGUIDE_REF = "For more info, refer to the user guide: \n" + USERGUIDE_URL; + public static final String ADD_HELP_HEADER = "Add contacts: "; + public static final String ADD_HELP_1 = "add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS d/DESCRIPTION [c/CLIENT_TYPE]"; + public static final String ADD_HELP_2 = "a n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS d/DESCRIPTION [c/CLIENT_TYPE]"; + public static final String LIST_HELP_HEADER = "List all contacts in ClientHub: "; + public static final String LIST_HELP = "list"; + public static final String EDIT_HELP_HEADER = "Edit contacts: "; + public static final String EDIT_HELP = "edit INDEX n/NAME p/PHONE e/EMAIL a/ADDRESS d/DESCRIPTION c/CLIENT_TYPE"; + public static final String DELETE_HELP_HEADER = "Delete contacts by name: "; + public static final String DELETE_HELP_1 = "delete NAME"; + public static final String DELETE_HELP_2 = "d NAME"; + public static final String DELETE_HELP_3 = "delete NAME/"; + public static final String CLEAR_HELP_HEADER = "Delete all contacts from ClientHub: "; + public static final String CLEAR_HELP = "clear"; + public static final String FN_HELP_HEADER = "Find contacts by name: "; + public static final String FN_HELP_1 = "find n/NAME"; + public static final String FN_HELP_2 = "fn NAME"; + public static final String FP_HELP_HEADER = "Find contacts by phone number: "; + public static final String FP_HELP_1 = "find p/PHONE"; + public static final String FP_HELP_2 = "fp PHONE"; + public static final String FA_HELP_HEADER = "Find contacts by address: "; + public static final String FA_HELP_1 = "find a/ADDRESS"; + public static final String FA_HELP_2 = "fa ADDRESS"; + public static final String FC_HELP_HEADER = "Find contacts by Client Type: "; + public static final String FC_HELP_1 = "find c/CLIENT_TYPE"; + public static final String FC_HELP_2 = "fc CLIENT_TYPE"; + public static final String SORT_HELP_HEADER = "Sort contacts by name: "; + public static final String SORT_HELP = "sort"; + public static final String VIEW_HELP_HEADER = "View a contact's full information: "; + public static final String VIEW_HELP_1 = "view NAME"; + public static final String VIEW_HELP_2 = "v NAME"; + public static final String RA_HELP_HEADER = "Add a reminder for a contact: "; + public static final String RA_HELP_1 = "radd n/NAME dt/DATE and TIME d/DESCRIPTION"; + public static final String RA_HELP_2 = "ra n/NAME dt/DATE and TIME d/DESCRIPTION"; + public static final String RE_HELP_HEADER = "Edit a reminder for a contact: "; + public static final String RE_HELP_1 = "redit INDEX dt/DATE and TIME d/DESCRIPTION"; + public static final String RE_HELP_2 = "re INDEX dt/DATE and TIME d/DESCRIPTION"; + public static final String RD_HELP_HEADER = "Delete a reminder for a contact: "; + public static final String RD_HELP_1 = "rdelete INDEX"; + public static final String RD_HELP_2 = "rd INDEX"; + public static final String EXIT_HELP_HEADER = "Exit and close ClientHub: "; + public static final String EXIT_HELP = "exit"; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); private static final String FXML = "HelpWindow.fxml"; @@ -27,6 +68,129 @@ public class HelpWindow extends UiPart { @FXML private Label helpMessage; + @FXML + private Label addHelpHeader; + + @FXML + private Label addHelp1; + + @FXML + private Label addHelp2; + + @FXML + private Label listHelpHeader; + + @FXML + private Label listHelp; + + @FXML + private Label editHelpHeader; + + @FXML + private Label editHelp; + + @FXML + private Label deleteHelpHeader; + + @FXML + private Label deleteHelp1; + + @FXML + private Label deleteHelp2; + + @FXML + private Label deleteHelp3; + + @FXML + private Label clearHelpHeader; + + @FXML + private Label clearHelp; + + @FXML + private Label fnHelpHeader; + + @FXML + private Label fnHelp1; + + @FXML + private Label fnHelp2; + + @FXML + private Label fpHelpHeader; + + @FXML + private Label fpHelp1; + + @FXML + private Label fpHelp2; + + @FXML + private Label faHelpHeader; + + @FXML + private Label faHelp1; + + @FXML + private Label faHelp2; + + @FXML + private Label fcHelpHeader; + + @FXML + private Label fcHelp1; + + @FXML + private Label fcHelp2; + + @FXML + private Label sortHelpHeader; + + @FXML + private Label sortHelp; + + @FXML + private Label viewHelpHeader; + + @FXML + private Label viewHelp1; + + @FXML + private Label viewHelp2; + + @FXML + private Label raHelpHeader; + + @FXML + private Label raHelp1; + + @FXML + private Label raHelp2; + + @FXML + private Label reHelpHeader; + + @FXML + private Label reHelp1; + + @FXML + private Label reHelp2; + + @FXML + private Label rdHelpHeader; + + @FXML + private Label rdHelp1; + + @FXML + private Label rdHelp2; + + @FXML + private Label exitHelpHeader; + + @FXML + private Label exitHelp; + /** * Creates a new HelpWindow. * @@ -34,7 +198,50 @@ public class HelpWindow extends UiPart { */ public HelpWindow(Stage root) { super(FXML, root); - helpMessage.setText(HELP_MESSAGE); + helpMessage.setText(USERGUIDE_REF); + + addHelpHeader.setText(ADD_HELP_HEADER); + listHelpHeader.setText(LIST_HELP_HEADER); + editHelpHeader.setText(EDIT_HELP_HEADER); + deleteHelpHeader.setText(DELETE_HELP_HEADER); + clearHelpHeader.setText(CLEAR_HELP_HEADER); + fnHelpHeader.setText(FN_HELP_HEADER); + fpHelpHeader.setText(FP_HELP_HEADER); + faHelpHeader.setText(FA_HELP_HEADER); + fcHelpHeader.setText(FC_HELP_HEADER); + sortHelpHeader.setText(SORT_HELP_HEADER); + viewHelpHeader.setText(VIEW_HELP_HEADER); + raHelpHeader.setText(RA_HELP_HEADER); + reHelpHeader.setText(RE_HELP_HEADER); + rdHelpHeader.setText(RD_HELP_HEADER); + exitHelpHeader.setText(EXIT_HELP_HEADER); + + addHelp1.setText(ADD_HELP_1); + addHelp2.setText(ADD_HELP_2); + listHelp.setText(LIST_HELP); + editHelp.setText(EDIT_HELP); + deleteHelp1.setText(DELETE_HELP_1); + deleteHelp2.setText(DELETE_HELP_2); + deleteHelp3.setText(DELETE_HELP_3); + clearHelp.setText(CLEAR_HELP); + fnHelp1.setText(FN_HELP_1); + fnHelp2.setText(FN_HELP_2); + fpHelp1.setText(FP_HELP_1); + fpHelp2.setText(FP_HELP_2); + faHelp1.setText(FA_HELP_1); + faHelp2.setText(FA_HELP_2); + fcHelp1.setText(FC_HELP_1); + fcHelp2.setText(FC_HELP_2); + sortHelp.setText(SORT_HELP); + viewHelp1.setText(VIEW_HELP_1); + viewHelp2.setText(VIEW_HELP_2); + raHelp1.setText(RA_HELP_1); + raHelp2.setText(RA_HELP_2); + reHelp1.setText(RE_HELP_1); + reHelp2.setText(RE_HELP_2); + rdHelp1.setText(RD_HELP_1); + rdHelp2.setText(RD_HELP_2); + exitHelp.setText(EXIT_HELP); } /** diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 79e74ef37c0..7a8122d3121 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -17,6 +17,7 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; + /** * The Main Window. Provides the basic application layout containing * a menu bar and space where other JavaFX elements can be placed. @@ -34,6 +35,8 @@ public class MainWindow extends UiPart { private PersonListPanel personListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; + private ViewPersonWindow viewPersonWindow; + private ReminderListPanel reminderListPanel; @FXML private StackPane commandBoxPlaceholder; @@ -50,6 +53,9 @@ public class MainWindow extends UiPart { @FXML private StackPane statusbarPlaceholder; + @FXML + private StackPane reminderListPanelPlaceholder; + /** * Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}. */ @@ -65,6 +71,7 @@ public MainWindow(Stage primaryStage, Logic logic) { setAccelerators(); + // Initialise windows helpWindow = new HelpWindow(); } @@ -110,17 +117,27 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { * Fills up all the placeholders of this window. */ void fillInnerParts() { - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); + // initialise person list panel + personListPanel = new PersonListPanel(logic.getPersonList()); personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + // initialise result display resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); + // initialise reminder list panel + reminderListPanel = new ReminderListPanel(logic.getReminderList()); + reminderListPanelPlaceholder.getChildren().add(reminderListPanel.getRoot()); + + // initialise status bar + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getClientHubFilePath()); statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); + // initialise command box CommandBox commandBox = new CommandBox(this::executeCommand); commandBoxPlaceholder.getChildren().add(commandBox.getRoot()); + + } /** @@ -160,9 +177,25 @@ private void handleExit() { (int) primaryStage.getX(), (int) primaryStage.getY()); logic.setGuiSettings(guiSettings); helpWindow.hide(); + + // If open, close the view person window + if (viewPersonWindow != null) { + viewPersonWindow.hide(); + } + primaryStage.hide(); } + @FXML + private void handleView() { + viewPersonWindow = new ViewPersonWindow(logic.getPersonList()); + if (!viewPersonWindow.isViewShowing()) { + viewPersonWindow.show(); + } else { + viewPersonWindow.focus(); + } + } + public PersonListPanel getPersonListPanel() { return personListPanel; } @@ -182,6 +215,10 @@ private CommandResult executeCommand(String commandText) throws CommandException handleHelp(); } + if (commandResult.isShowView()) { + handleView(); + } + if (commandResult.isExit()) { handleExit(); } diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java index 094c42cda82..f41ad3f21c6 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCard.java @@ -39,7 +39,7 @@ public class PersonCard extends UiPart { @FXML private Label email; @FXML - private FlowPane tags; + private FlowPane clientTypes; /** * Creates a {@code PersonCode} with the given {@code Person} and index to display. @@ -52,8 +52,8 @@ public PersonCard(Person person, int displayedIndex) { phone.setText(person.getPhone().value); address.setText(person.getAddress().value); email.setText(person.getEmail().value); - person.getTags().stream() - .sorted(Comparator.comparing(tag -> tag.tagName)) - .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + person.getClientTypes().stream() + .sorted(Comparator.comparing(clientType -> clientType.clientTypeName)) + .forEach(clientType -> clientTypes.getChildren().add(new Label(clientType.clientTypeName))); } } diff --git a/src/main/java/seedu/address/ui/ReminderCard.java b/src/main/java/seedu/address/ui/ReminderCard.java new file mode 100644 index 00000000000..33b19114483 --- /dev/null +++ b/src/main/java/seedu/address/ui/ReminderCard.java @@ -0,0 +1,63 @@ +package seedu.address.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.reminder.Reminder; + +/** + * An UI component that displays information of a {@code Reminder}. + */ +public class ReminderCard extends UiPart { + + private static final String FXML = "ReminderCard.fxml"; + + public final Reminder reminder; + + @FXML + private HBox reminderCardPane; + @FXML + private Label id; + @FXML + private Label personName; + @FXML + private Label description; + @FXML + private Label time; + @FXML + private Label date; + + /** + * Creates a {@code ReminderCard} with dummy data using the given index. + */ + public ReminderCard(Reminder reminder, int displayedIndex) { + super(FXML); + this.reminder = reminder; + id.setText(displayedIndex + ". "); + personName.setText(reminder.getPersonName()); + description.setText(reminder.getDescription().description); + + // Creates a more readable format for the date and time + String dateString = reminder.getDateTime().toLocalDate().toString(); // "2021-12-21" + String timeString = reminder.getDateTime().toLocalTime().toString(); // "23:59:00" + time.setText(timeString); + date.setText(dateString); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof ReminderCard)) { + return false; + } + + ReminderCard card = (ReminderCard) other; + return id.getText().equals(card.id.getText()) + && personName.getText().equals(card.personName.getText()) + && description.getText().equals(card.description.getText()); + } +} diff --git a/src/main/java/seedu/address/ui/ReminderListPanel.java b/src/main/java/seedu/address/ui/ReminderListPanel.java new file mode 100644 index 00000000000..010fd12a514 --- /dev/null +++ b/src/main/java/seedu/address/ui/ReminderListPanel.java @@ -0,0 +1,49 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.reminder.Reminder; + +/** + * Panel containing the list of reminders. + */ +public class ReminderListPanel extends UiPart { + private static final String FXML = "ReminderListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(ReminderListPanel.class); + + @FXML + private ListView reminderListView; + + /** + * Creates a {@code ReminderListPanel} with the given {@code ObservableList}. + */ + public ReminderListPanel(ObservableList reminderList) { + super(FXML); + reminderListView.setItems(reminderList); + reminderListView.setCellFactory(listView -> new ReminderListViewCell()); + + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Reminder} using a {@code ReminderCard}. + */ + class ReminderListViewCell extends ListCell { + @Override + protected void updateItem(Reminder reminder, boolean empty) { + super.updateItem(reminder, empty); + + if (empty || reminder == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new ReminderCard(reminder, getIndex() + 1).getRoot()); + } + } + } +} diff --git a/src/main/java/seedu/address/ui/ViewPersonWindow.java b/src/main/java/seedu/address/ui/ViewPersonWindow.java new file mode 100644 index 00000000000..8d1f971a514 --- /dev/null +++ b/src/main/java/seedu/address/ui/ViewPersonWindow.java @@ -0,0 +1,82 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.stage.Stage; +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.person.Person; + +/** + * Controller for a view person page + */ +public class ViewPersonWindow extends UiPart { + + + public static final Logger LOGGER = LogsCenter.getLogger(ViewPersonWindow.class); + public static final String FXML = "ViewPersonWindow.fxml"; + + public final ObservableList person; + + // Fields for the client + @FXML + private Label name; + @FXML + private Label phone; + @FXML + private Label address; + @FXML + private Label email; + @FXML + private Label description; + @FXML + private FlowPane clientTypes; + + /** + * Creates a new ViewPersonWindow. + * + * @param person ObservableList of persons to view + */ + public ViewPersonWindow(ObservableList person) { + super(FXML, new Stage()); + this.person = person; + + // Initialising values for the client + // Using streams to get the first person and set values + person.stream() + .findFirst() + .ifPresent(p -> { + name.setText(p.getName().fullName); + phone.setText(p.getPhone().value); + address.setText(p.getAddress().value); + email.setText(p.getEmail().value); + p.getClientTypes().forEach(clientType -> + clientTypes.getChildren().add(new Label(clientType.clientTypeName))); + description.setText(p.getDescription().description); + }); + } + + /** + * Shows the ViewPersonWindow. + */ + public void show() { + LOGGER.fine("Viewing client details."); + getRoot().show(); + getRoot().centerOnScreen(); + } + + public boolean isViewShowing() { + return getRoot().isShowing(); + } + + public void hide() { + getRoot().hide(); + } + + public void focus() { + getRoot().requestFocus(); + } +} diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 36e6b001cd8..2ae86f6894c 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -26,7 +26,7 @@ .text-field { -fx-font-size: 12pt; - -fx-font-family: "Segoe UI Semibold"; + -fx-font-family: "Arial Rounded MT Bold"; } .tab-pane { @@ -94,40 +94,48 @@ } .list-cell { - -fx-label-padding: 0 0 0 0; + -fx-label-padding: 5 0 5 0; -fx-graphic-text-gap : 0; - -fx-padding: 0 0 0 0; + -fx-padding: 2 0 2 0; + -fx-background-insets: 10 0 10 0; + -fx-background-radius: 15; + -fx-border-radius: 15; } .list-cell:filled:even { - -fx-background-color: #3c3e3f; + -fx-background-color: #ffffff; } .list-cell:filled:odd { - -fx-background-color: #515658; + -fx-background-color: #ffffff; } .list-cell:filled:selected { - -fx-background-color: #424d5f; + -fx-background-color: #b0b0c0; } .list-cell:filled:selected #cardPane { - -fx-border-color: #3e7b91; + /*-fx-border-color: #151414;*/ -fx-border-width: 1; } .list-cell .label { - -fx-text-fill: white; + -fx-text-fill: black; } +/* The following styles are used in the PersonCard */ +/* For the name attribute */ .cell_big_label { - -fx-font-family: "Segoe UI Semibold"; - -fx-font-size: 16px; + /*-fx-font-family: "Segoe UI Semibold";*/ + -fx-font-family: "Arial Rounded MT Bold"; + -fx-font-size: 20px; -fx-text-fill: #010504; + -fx-font-weight: bold; } +/* For the other attributes */ .cell_small_label { - -fx-font-family: "Segoe UI"; + -fx-font-family: "Arial Rounded MT Bold"; -fx-font-size: 13px; -fx-text-fill: #010504; } @@ -148,7 +156,7 @@ .result-display { -fx-background-color: transparent; - -fx-font-family: "Segoe UI Light"; + -fx-font-family: "Arial Rounded MT Bold"; -fx-font-size: 13pt; -fx-text-fill: white; } @@ -310,6 +318,8 @@ #cardPane { -fx-background-color: transparent; -fx-border-width: 0; + -fx-background-radius: 20; + -fx-border-radius: 20; } #commandTypeLabel { @@ -323,7 +333,7 @@ -fx-border-color: #383838 #383838 #ffffff #383838; -fx-border-insets: 0; -fx-border-width: 1; - -fx-font-family: "Segoe UI Light"; + -fx-font-family: "Arial Rounded MT Bold"; -fx-font-size: 13pt; -fx-text-fill: white; } @@ -337,16 +347,31 @@ -fx-background-radius: 0; } -#tags { +#clientTypes { -fx-hgap: 7; -fx-vgap: 3; } -#tags .label { - -fx-text-fill: white; - -fx-background-color: #3e7b91; +#clientTypes .label { + -fx-text-fill: #ffffff; + -fx-background-color: #9156bb; -fx-padding: 1 3 1 3; - -fx-border-radius: 2; + -fx-border-radius: 10; -fx-background-radius: 2; -fx-font-size: 11; + -fx-font-family: "Arial Rounded MT Bold"; } + +.reminder-card { + -fx-background-color: transparent; + -fx-border-width: 0 0 1 0; + -fx-border-color: derive(#1d1d1d, 20%); + -fx-padding: 6 4 6 4; +} + +.cell_small_label_bold { + -fx-font-weight: bold; + -fx-text-fill: red; +} + + diff --git a/src/main/resources/view/HelpWindow.css b/src/main/resources/view/HelpWindow.css index 17e8a8722cd..330ce479be5 100644 --- a/src/main/resources/view/HelpWindow.css +++ b/src/main/resources/view/HelpWindow.css @@ -1,13 +1,17 @@ -#copyButton, #helpMessage { - -fx-text-fill: white; +#helpMessage{ + -fx-text-fill: black; } #copyButton { - -fx-background-color: dimgray; + -fx-text-fill: white; + -fx-font-family: "Arial Rounded MT Bold"; + -fx-font-weight: bold; + -fx-background-color: #a232ea; } + #copyButton:hover { - -fx-background-color: gray; + -fx-background-color: #570987; } #copyButton:armed { @@ -15,5 +19,58 @@ } #helpMessageContainer { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #ffffff; +} + +#moreInfoContainer { + -fx-background-color: #ffffff; + -fx-font-family: "Arial Rounded MT Bold"; + -fx-min-height: 80; +} + +.helpMessageWrapper{ + -fx-background-color: #dedede; + -fx-min-height: 5; + -fx-min-width: 100; + -fx-content-display: CENTER; + -fx-alignment: CENTER; + -fx-background-radius: 10; + -fx-border-radius: 10; + -fx-padding: 15; + +} + +.commandContainer { + -fx-background-color: transparent; + -fx-border-width: 0; + -fx-background-radius: 20; + -fx-border-radius: 20; +} + +.headerLabel { + -fx-font-family: "Arial Rounded MT Bold"; + -fx-font-size: 20px; + -fx-text-fill: #000000; + -fx-font-weight: bold; +} + +.commandLabel { + -fx-font-family: "Andale Mono"; + -fx-font-size: 15px; + -fx-text-fill: black; +} + +.orLabel { + -fx-font-family: "Arial Rounded MT Bold"; + -fx-font-size: 15px; + -fx-text-fill: black; + -fx-font-weight: bold; +} + +.commandWrapper { + -fx-background-color: #f5f5f5; + -fx-padding: 10; + -fx-background-radius: 5; + -fx-pref-width: 300; + -fx-pref-height: 100; } diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml index e01f330de33..6a3a24aaf2e 100644 --- a/src/main/resources/view/HelpWindow.fxml +++ b/src/main/resources/view/HelpWindow.fxml @@ -7,9 +7,12 @@ + + - + + @@ -19,26 +22,225 @@ - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 7778f666a0a..81aee2f08db 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -6,24 +6,29 @@ - + + + title="ClientHub" minWidth="1000" minHeight="800" onCloseRequest="#handleExit"> + - + + + +

@@ -33,12 +38,14 @@ + + @@ -46,12 +53,24 @@ - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index 84e09833a87..f2e07a69f05 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -14,11 +14,11 @@ - + - + - +